Refactor policies document

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-01-02 11:04:48 +01:00
parent cbf338fd14
commit 4013b00841
75 changed files with 1958 additions and 1230 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<83cc00d864e6388ef2156203499da849>> * @generated SignedSource<<76057cc6f78c32b15c6e580c7a0c30da>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -124,13 +124,6 @@ return {
"name": "expiresAt", "name": "expiresAt",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastUsedAt",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -194,12 +187,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "14e698e7b6edf0afe9cf3398986cd529", "cacheID": "cf6f6f621bdcb1bd24a2dd94f856729f",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "APIKeysPageQuery", "name": "APIKeysPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query APIKeysPageQuery {\n viewer {\n ...PersonalAPIKeyListFragment\n id\n }\n}\n\nfragment PersonalAPIKeyListFragment on Identity {\n id\n personalAPIKeys(first: 1000) {\n edges {\n node {\n id\n name\n createdAt\n expiresAt\n lastUsedAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" "text": "query APIKeysPageQuery {\n viewer {\n ...PersonalAPIKeyListFragment\n id\n }\n}\n\nfragment PersonalAPIKeyListFragment on Identity {\n id\n personalAPIKeys(first: 1000) {\n edges {\n node {\n id\n ...PersonalAPIKeyRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment PersonalAPIKeyRowFragment on PersonalAPIKey {\n id\n name\n createdAt\n expiresAt\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<875eae6f5455a17a1bb8e0590c7acdb0>> * @generated SignedSource<<faeeb8835f77a5251a0311dbac21bfde>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -12,7 +12,6 @@ import { ConcreteRequest } from 'relay-runtime';
export type CreatePersonalAPIKeyInput = { export type CreatePersonalAPIKeyInput = {
expiresAt: any; expiresAt: any;
name: string; name: string;
organizationIds: ReadonlyArray<string>;
}; };
export type PersonalAPIKeyListCreateMutation$variables = { export type PersonalAPIKeyListCreateMutation$variables = {
connections: ReadonlyArray<string>; connections: ReadonlyArray<string>;
@@ -25,7 +24,6 @@ export type PersonalAPIKeyListCreateMutation$data = {
readonly createdAt: any; readonly createdAt: any;
readonly expiresAt: any; readonly expiresAt: any;
readonly id: string; readonly id: string;
readonly lastUsedAt: any | null | undefined;
readonly name: string; readonly name: string;
}; };
}; };
@@ -98,13 +96,6 @@ v3 = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "expiresAt", "name": "expiresAt",
"storageKey": null "storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastUsedAt",
"storageKey": null
} }
], ],
"storageKey": null "storageKey": null
@@ -187,16 +178,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "5b1f6109730bd9f261afd6635d568b59", "cacheID": "1ec09abe9f3a61e13ba2deacf1a273fb",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PersonalAPIKeyListCreateMutation", "name": "PersonalAPIKeyListCreateMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation PersonalAPIKeyListCreateMutation(\n $input: CreatePersonalAPIKeyInput!\n) {\n createPersonalAPIKey(input: $input) {\n personalAPIKeyEdge {\n node {\n id\n name\n createdAt\n expiresAt\n lastUsedAt\n }\n }\n token\n }\n}\n" "text": "mutation PersonalAPIKeyListCreateMutation(\n $input: CreatePersonalAPIKeyInput!\n) {\n createPersonalAPIKey(input: $input) {\n personalAPIKeyEdge {\n node {\n id\n name\n createdAt\n expiresAt\n }\n }\n token\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "c7e932ad41ff2740e8687b7edb904431"; (node as any).hash = "86df62e5e5f2d9f347f41648d7af9c32";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<e518a98137eb07e37499d945991b15ed>> * @generated SignedSource<<bdb7bb502fae96e1eb5b8ae644f8bebf>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -15,11 +15,8 @@ export type PersonalAPIKeyListFragment$data = {
readonly personalAPIKeys: { readonly personalAPIKeys: {
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly createdAt: any;
readonly expiresAt: any;
readonly id: string; readonly id: string;
readonly lastUsedAt: any | null | undefined; readonly " $fragmentSpreads": FragmentRefs<"PersonalAPIKeyRowFragment">;
readonly name: string;
}; };
}>; }>;
}; };
@@ -86,32 +83,9 @@ return {
"selections": [ "selections": [
(v0/*: any*/), (v0/*: any*/),
{ {
"alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "FragmentSpread",
"name": "name", "name": "PersonalAPIKeyRowFragment"
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastUsedAt",
"storageKey": null
}, },
{ {
"alias": null, "alias": null,
@@ -171,6 +145,6 @@ return {
}; };
})(); })();
(node as any).hash = "70fa6b4e568fc1598b8dc23a030428b5"; (node as any).hash = "eac59db5cdb5d77813236a90e7fb4d94";
export default node; export default node;

View File

@@ -1,92 +0,0 @@
/**
* @generated SignedSource<<7a6fb678e9147fd1810e02bd09d5cef1>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RevealPersonalAPIKeyTokenInput = {
tokenId: string;
};
export type PersonalAPIKeyListRevealTokenMutation$variables = {
input: RevealPersonalAPIKeyTokenInput;
};
export type PersonalAPIKeyListRevealTokenMutation$data = {
readonly revealPersonalAPIKeyToken: {
readonly token: string;
} | null | undefined;
};
export type PersonalAPIKeyListRevealTokenMutation = {
response: PersonalAPIKeyListRevealTokenMutation$data;
variables: PersonalAPIKeyListRevealTokenMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "RevealPersonalAPIKeyTokenPayload",
"kind": "LinkedField",
"name": "revealPersonalAPIKeyToken",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "token",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PersonalAPIKeyListRevealTokenMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PersonalAPIKeyListRevealTokenMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "25ffba7c8a69e22dcc46b56a6c8633ed",
"id": null,
"metadata": {},
"name": "PersonalAPIKeyListRevealTokenMutation",
"operationKind": "mutation",
"text": "mutation PersonalAPIKeyListRevealTokenMutation(\n $input: RevealPersonalAPIKeyTokenInput!\n) {\n revealPersonalAPIKeyToken(input: $input) {\n token\n }\n}\n"
}
};
})();
(node as any).hash = "7dad7ebd6e8086a01b2bac8aa80c532e";
export default node;

View File

@@ -1,92 +0,0 @@
/**
* @generated SignedSource<<84eef5bca22c2ad2b73bbe068767e35a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RevokePersonalAPIKeyInput = {
tokenId: string;
};
export type PersonalAPIKeyListRevokeMutation$variables = {
input: RevokePersonalAPIKeyInput;
};
export type PersonalAPIKeyListRevokeMutation$data = {
readonly revokePersonalAPIKey: {
readonly success: boolean;
} | null | undefined;
};
export type PersonalAPIKeyListRevokeMutation = {
response: PersonalAPIKeyListRevokeMutation$data;
variables: PersonalAPIKeyListRevokeMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "RevokePersonalAPIKeyPayload",
"kind": "LinkedField",
"name": "revokePersonalAPIKey",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PersonalAPIKeyListRevokeMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PersonalAPIKeyListRevokeMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "fd05bb0236b583ca54be466cffe8f45b",
"id": null,
"metadata": {},
"name": "PersonalAPIKeyListRevokeMutation",
"operationKind": "mutation",
"text": "mutation PersonalAPIKeyListRevokeMutation(\n $input: RevokePersonalAPIKeyInput!\n) {\n revokePersonalAPIKey(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "34ef04d19251c479827ca5028346f65b";
export default node;

View File

@@ -0,0 +1,101 @@
/**
* @generated SignedSource<<ed51014f8f35d76faaa63c10998610be>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PersonalAPIKeyRowFragment$data = {
readonly createdAt: any;
readonly expiresAt: any;
readonly id: string;
readonly name: string;
readonly token?: string | null | undefined;
readonly " $fragmentType": "PersonalAPIKeyRowFragment";
};
export type PersonalAPIKeyRowFragment$key = {
readonly " $data"?: PersonalAPIKeyRowFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"PersonalAPIKeyRowFragment">;
};
import PersonalAPIKeyRowRefetchQuery_graphql from './PersonalAPIKeyRowRefetchQuery.graphql';
const node: ReaderFragment = {
"argumentDefinitions": [
{
"defaultValue": false,
"kind": "LocalArgument",
"name": "includeToken"
}
],
"kind": "Fragment",
"metadata": {
"refetch": {
"connection": null,
"fragmentPathInResult": [
"node"
],
"operation": PersonalAPIKeyRowRefetchQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "PersonalAPIKeyRowFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"condition": "includeToken",
"kind": "Condition",
"passingValue": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "token",
"storageKey": null
}
]
}
],
"type": "PersonalAPIKey",
"abstractKey": null
};
(node as any).hash = "d17db443fa203ee5f9c7d0f4576295f0";
export default node;

View File

@@ -0,0 +1,172 @@
/**
* @generated SignedSource<<2d413d4e4cf2528a45ff243c0fa733bb>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PersonalAPIKeyRowRefetchQuery$variables = {
id: string;
includeToken?: boolean | null | undefined;
};
export type PersonalAPIKeyRowRefetchQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"PersonalAPIKeyRowFragment">;
} | null | undefined;
};
export type PersonalAPIKeyRowRefetchQuery = {
response: PersonalAPIKeyRowRefetchQuery$data;
variables: PersonalAPIKeyRowRefetchQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
},
v1 = {
"defaultValue": false,
"kind": "LocalArgument",
"name": "includeToken"
},
v2 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
];
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "PersonalAPIKeyRowRefetchQuery",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": [
{
"kind": "Variable",
"name": "includeToken",
"variableName": "includeToken"
}
],
"kind": "FragmentSpread",
"name": "PersonalAPIKeyRowFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "PersonalAPIKeyRowRefetchQuery",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"condition": "includeToken",
"kind": "Condition",
"passingValue": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "token",
"storageKey": null
}
]
}
],
"type": "PersonalAPIKey",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "b4732244e28d5847baaa5520a95a4cfb",
"id": null,
"metadata": {},
"name": "PersonalAPIKeyRowRefetchQuery",
"operationKind": "query",
"text": "query PersonalAPIKeyRowRefetchQuery(\n $includeToken: Boolean = false\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PersonalAPIKeyRowFragment_2T7Twf\n id\n }\n}\n\nfragment PersonalAPIKeyRowFragment_2T7Twf on PersonalAPIKey {\n id\n name\n createdAt\n expiresAt\n token @include(if: $includeToken)\n}\n"
}
};
})();
(node as any).hash = "d17db443fa203ee5f9c7d0f4576295f0";
export default node;

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<64ad5ad6ff333dffc7b2d5d578028d7f>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RevokePersonalAPIKeyInput = {
personalAPIKeyId: string;
};
export type PersonalAPIKeyRow_revokeMutation$variables = {
connections: ReadonlyArray<string>;
input: RevokePersonalAPIKeyInput;
};
export type PersonalAPIKeyRow_revokeMutation$data = {
readonly revokePersonalAPIKey: {
readonly personalAPIKeyId: string;
} | null | undefined;
};
export type PersonalAPIKeyRow_revokeMutation = {
response: PersonalAPIKeyRow_revokeMutation$data;
variables: PersonalAPIKeyRow_revokeMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "personalAPIKeyId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "PersonalAPIKeyRow_revokeMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "RevokePersonalAPIKeyPayload",
"kind": "LinkedField",
"name": "revokePersonalAPIKey",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "PersonalAPIKeyRow_revokeMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "RevokePersonalAPIKeyPayload",
"kind": "LinkedField",
"name": "revokePersonalAPIKey",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "personalAPIKeyId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "29dfd9de3537585482c2863a1d547031",
"id": null,
"metadata": {},
"name": "PersonalAPIKeyRow_revokeMutation",
"operationKind": "mutation",
"text": "mutation PersonalAPIKeyRow_revokeMutation(\n $input: RevokePersonalAPIKeyInput!\n) {\n revokePersonalAPIKey(input: $input) {\n personalAPIKeyId\n }\n}\n"
}
};
})();
(node as any).hash = "258ca02064f55b6fd4a3630ba1f904a4";
export default node;

View File

@@ -6,10 +6,9 @@ import {
useFragment, useFragment,
useMutation, useMutation,
} from "react-relay"; } from "react-relay";
import type { RecordSourceSelectorProxy } from "relay-runtime";
import { z } from "zod"; import { z } from "zod";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { formatError, type GraphQLError } from "@probo/helpers"; import { formatError } from "@probo/helpers";
import { import {
Breadcrumb, Breadcrumb,
Button, Button,
@@ -22,15 +21,12 @@ import {
Label, Label,
Option, Option,
Select, Select,
useConfirm,
useDialogRef, useDialogRef,
useToast, useToast,
} from "@probo/ui"; } from "@probo/ui";
import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useFormWithSchema } from "/hooks/useFormWithSchema";
import type { PersonalAPIKeyListFragment$key } from "/__generated__/iam/PersonalAPIKeyListFragment.graphql"; import type { PersonalAPIKeyListFragment$key } from "/__generated__/iam/PersonalAPIKeyListFragment.graphql";
import type { PersonalAPIKeyListCreateMutation } from "/__generated__/iam/PersonalAPIKeyListCreateMutation.graphql"; import type { PersonalAPIKeyListCreateMutation } from "/__generated__/iam/PersonalAPIKeyListCreateMutation.graphql";
import type { PersonalAPIKeyListRevokeMutation } from "/__generated__/iam/PersonalAPIKeyListRevokeMutation.graphql";
import type { PersonalAPIKeyListRevealTokenMutation } from "/__generated__/iam/PersonalAPIKeyListRevealTokenMutation.graphql";
import { PersonalAPIKeysTable } from "./PersonalAPIKeysTable"; import { PersonalAPIKeysTable } from "./PersonalAPIKeysTable";
import { PersonalAPIKeyTokenDialog } from "./PersonalAPIKeyTokenDialog"; import { PersonalAPIKeyTokenDialog } from "./PersonalAPIKeyTokenDialog";
@@ -44,10 +40,7 @@ const fragment = graphql`
edges @required(action: THROW) { edges @required(action: THROW) {
node { node {
id id
name ...PersonalAPIKeyRowFragment
createdAt
expiresAt
lastUsedAt
} }
} }
} }
@@ -66,7 +59,6 @@ const createMutation = graphql`
name name
createdAt createdAt
expiresAt expiresAt
lastUsedAt
} }
} }
token token
@@ -74,26 +66,6 @@ const createMutation = graphql`
} }
`; `;
const revokeMutation = graphql`
mutation PersonalAPIKeyListRevokeMutation(
$input: RevokePersonalAPIKeyInput!
) {
revokePersonalAPIKey(input: $input) {
success
}
}
`;
const revealTokenMutation = graphql`
mutation PersonalAPIKeyListRevealTokenMutation(
$input: RevealPersonalAPIKeyTokenInput!
) {
revealPersonalAPIKeyToken(input: $input) {
token
}
}
`;
const createSchema = z.object({ const createSchema = z.object({
name: z.string().min(1, "Name is required"), name: z.string().min(1, "Name is required"),
expiresIn: z.enum(["1month", "3months", "6months", "1year"]), expiresIn: z.enum(["1month", "3months", "6months", "1year"]),
@@ -126,7 +98,6 @@ export function PersonalAPIKeyList(props: {
const { fKey } = props; const { fKey } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
const { toast } = useToast(); const { toast } = useToast();
const confirm = useConfirm();
const createDialogRef = useDialogRef(); const createDialogRef = useDialogRef();
const tokenDialogRef = useDialogRef(); const tokenDialogRef = useDialogRef();
@@ -134,9 +105,12 @@ export function PersonalAPIKeyList(props: {
const viewer = useFragment(fragment, fKey); const viewer = useFragment(fragment, fKey);
const keys = viewer.personalAPIKeys.edges.map(({ node }) => node); const connectionID = ConnectionHandler.getConnectionID(
viewer.id,
"PersonalAPIKeyListFragment_personalAPIKeys"
);
const { formState, handleSubmit, register, control, reset, watch } = const { formState, handleSubmit, register, control, reset } =
useFormWithSchema(createSchema, { useFormWithSchema(createSchema, {
defaultValues: { defaultValues: {
name: new Date().toISOString().split("T")[0], name: new Date().toISOString().split("T")[0],
@@ -144,20 +118,14 @@ export function PersonalAPIKeyList(props: {
}, },
}); });
watch();
const [createCommit, isCreating] = const [createCommit, isCreating] =
useMutation<PersonalAPIKeyListCreateMutation>(createMutation); useMutation<PersonalAPIKeyListCreateMutation>(createMutation);
const [revokeCommit] =
useMutation<PersonalAPIKeyListRevokeMutation>(revokeMutation);
const [revealTokenCommit, isRevealingToken] =
useMutation<PersonalAPIKeyListRevealTokenMutation>(revealTokenMutation);
const handleCreate = (data: CreateFormData) => { const handleCreate = (data: CreateFormData) => {
const expiresAt = computeExpiresAt(data.expiresIn); const expiresAt = computeExpiresAt(data.expiresIn);
const connectionID = ConnectionHandler.getConnectionID( const connectionID = ConnectionHandler.getConnectionID(
viewer.id, viewer.id,
"PersonalAPIKeyListFragment_personalAPIKeys", "PersonalAPIKeyListFragment_personalAPIKeys"
); );
createCommit({ createCommit({
@@ -165,8 +133,6 @@ export function PersonalAPIKeyList(props: {
input: { input: {
name: data.name, name: data.name,
expiresAt: expiresAt.toISOString(), expiresAt: expiresAt.toISOString(),
// API keys are no longer linked to organizations; keep schema compatibility.
organizationIds: [],
}, },
connections: [connectionID], connections: [connectionID],
}, },
@@ -194,116 +160,6 @@ export function PersonalAPIKeyList(props: {
}); });
}; };
const handleRevoke = (key: { id: string; name: string }) => {
confirm(
async () => {
await new Promise<void>((resolve, reject) => {
revokeCommit({
variables: {
input: { tokenId: key.id },
},
updater: (store: RecordSourceSelectorProxy) => {
const viewerRecord = store.getRoot().getLinkedRecord("viewer");
if (!viewerRecord) return;
const connection = ConnectionHandler.getConnection(
viewerRecord,
"PersonalAPIKeyListFragment_personalAPIKeys",
);
if (connection) {
ConnectionHandler.deleteNode(connection, key.id);
}
},
onCompleted: (_response, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(
__("Failed to revoke API key."),
errors as GraphQLError[],
),
variant: "error",
});
reject(errors);
return;
}
toast({
title: __("Success"),
description: __("API key revoked successfully."),
variant: "success",
});
resolve();
},
onError: (error) => {
toast({
title: __("Error"),
description: formatError(
__("Failed to revoke API key."),
error,
),
variant: "error",
});
reject(error);
},
});
});
},
{
title: __("Revoke API Key"),
message: __(
`Are you sure you want to revoke the API key "${key.name}"? This action cannot be undone.`,
),
label: __("Revoke"),
variant: "danger",
},
);
};
const handleShowToken = (key: { id: string; name: string }) => {
revealTokenCommit({
variables: {
input: {
tokenId: key.id,
},
},
onCompleted: (response, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(
__("Failed to reveal API key token."),
errors as any,
),
variant: "error",
});
return;
}
const tokenValue = response.revealPersonalAPIKeyToken?.token;
if (!tokenValue) {
toast({
title: __("Error"),
description: __("No token returned."),
variant: "error",
});
return;
}
setToken(tokenValue);
tokenDialogRef.current?.open();
},
onError: (error: Error) => {
toast({
title: __("Error"),
description: formatError(
__("Failed to reveal API key token."),
error,
),
variant: "error",
});
},
});
};
return ( return (
<> <>
<div className="space-y-4"> <div className="space-y-4">
@@ -314,7 +170,7 @@ export function PersonalAPIKeyList(props: {
</Button> </Button>
</div> </div>
{keys.length === 0 ? ( {viewer.personalAPIKeys.edges.length === 0 ? (
<Card padded> <Card padded>
<div className="text-center py-12"> <div className="text-center py-12">
<h3 className="text-lg font-medium text-gray-900 mb-2"> <h3 className="text-lg font-medium text-gray-900 mb-2">
@@ -328,10 +184,8 @@ export function PersonalAPIKeyList(props: {
) : ( ) : (
<Card padded> <Card padded>
<PersonalAPIKeysTable <PersonalAPIKeysTable
keys={keys} edges={viewer.personalAPIKeys.edges}
onRevoke={handleRevoke} connectionId={connectionID}
onShowToken={handleShowToken}
isShowingToken={isRevealingToken}
/> />
</Card> </Card>
)} )}

View File

@@ -0,0 +1,135 @@
import { useTranslate } from "@probo/i18n";
import { formatDate, formatError, type GraphQLError } from "@probo/helpers";
import { Button, Spinner, Td, Tr, useConfirm, useToast } from "@probo/ui";
import { graphql } from "relay-runtime";
import { useMutation, useFragment } from "react-relay";
import type { PersonalAPIKeyRowFragment$key } from "/__generated__/iam/PersonalAPIKeyRowFragment.graphql";
import type { PersonalAPIKeyRow_revokeMutation } from "/__generated__/iam/PersonalAPIKeyRow_revokeMutation.graphql";
import clsx from "clsx";
import { PersonalAPIKeyTokenAction } from "./PersonalAPIKeyTokenAction";
import { Suspense } from "react";
const revokeMutation = graphql`
mutation PersonalAPIKeyRow_revokeMutation(
$input: RevokePersonalAPIKeyInput!
$connections: [ID!]!
) {
revokePersonalAPIKey(input: $input) {
personalAPIKeyId @deleteEdge(connections: $connections)
}
}
`;
export const personalAPIKeyRowFragment = graphql`
fragment PersonalAPIKeyRowFragment on PersonalAPIKey
@refetchable(queryName: "PersonalAPIKeyRowRefetchQuery")
@argumentDefinitions(includeToken: { type: "Boolean", defaultValue: false }) {
id
name
createdAt
expiresAt
token @include(if: $includeToken)
}
`;
export function PersonalAPIKeyRow(props: {
fKey: PersonalAPIKeyRowFragment$key;
connectionId: string;
}) {
const { fKey, connectionId } = props;
const { __ } = useTranslate();
const confirm = useConfirm();
const { toast } = useToast();
const now = new Date();
const key = useFragment(personalAPIKeyRowFragment, fKey);
const expired = new Date(key.expiresAt) < now;
const [revokeCommit, isRevoking] =
useMutation<PersonalAPIKeyRow_revokeMutation>(revokeMutation);
const handleRevoke = () => {
confirm(
async () => {
await new Promise<void>((resolve, reject) => {
revokeCommit({
variables: {
input: { personalAPIKeyId: key.id },
connections: [connectionId],
},
onCompleted: (_response, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(
__("Failed to revoke API key."),
errors as GraphQLError[]
),
variant: "error",
});
reject(errors);
return;
}
toast({
title: __("Success"),
description: __("API key revoked successfully."),
variant: "success",
});
resolve();
},
onError: (error) => {
toast({
title: __("Error"),
description: formatError(
__("Failed to revoke API key."),
error
),
variant: "error",
});
reject(error);
},
});
});
},
{
title: __("Revoke API Key"),
message: __(
`Are you sure you want to revoke the API key "${key.name}"? This action cannot be undone.`
),
label: __("Revoke"),
variant: "danger",
}
);
};
return (
<Tr className={clsx(isRevoking && "opacity-60 pointer-events-none")}>
<Td>
<div className="font-medium text-txt-primary">{key.name}</div>
<div className="text-xs text-txt-tertiary">
{expired ? __("Expired") : __("Active")}
</div>
</Td>
<Td>
<span className="text-sm text-txt-secondary">
{formatDate(key.createdAt)}
</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary">
{formatDate(key.expiresAt)}
</span>
</Td>
<Td width={140} className="text-end">
<div className="flex gap-2 justify-end">
<Suspense fallback={<Spinner />}>
<PersonalAPIKeyTokenAction fKey={fKey} disabled={isRevoking} />
</Suspense>
<Button variant="danger" onClick={handleRevoke} disabled={isRevoking}>
{__("Revoke")}
</Button>
</div>
</Td>
</Tr>
);
}

View File

@@ -0,0 +1,63 @@
import { useTranslate } from "@probo/i18n";
import { Button, useDialogRef, useToast } from "@probo/ui";
import { formatError } from "@probo/helpers";
import { useRefetchableFragment } from "react-relay";
import { PersonalAPIKeyTokenDialog } from "./PersonalAPIKeyTokenDialog";
import { personalAPIKeyRowFragment } from "./PersonalAPIKeyRow";
import type { PersonalAPIKeyRowFragment$key } from "/__generated__/iam/PersonalAPIKeyRowFragment.graphql";
import type { PersonalAPIKeyRowRefetchQuery } from "/__generated__/iam/PersonalAPIKeyRowRefetchQuery.graphql";
export function PersonalAPIKeyTokenAction(props: {
fKey: PersonalAPIKeyRowFragment$key;
disabled?: boolean;
}) {
const { fKey, disabled } = props;
const { __ } = useTranslate();
const { toast } = useToast();
const dialogRef = useDialogRef();
const [data, refetch] = useRefetchableFragment<
PersonalAPIKeyRowRefetchQuery,
PersonalAPIKeyRowFragment$key
>(personalAPIKeyRowFragment, fKey);
const handleShow = () => {
dialogRef.current?.open();
refetch(
{ includeToken: true },
{
fetchPolicy: "network-only",
onComplete: (error) => {
if (error) {
toast({
title: __("Error"),
description: formatError(
__("Failed to load API key token."),
error
),
variant: "error",
});
dialogRef.current?.close();
}
},
}
);
};
return (
<>
<Button variant="secondary" onClick={handleShow} disabled={!!disabled}>
{__("Show")}
</Button>
<PersonalAPIKeyTokenDialog
dialogRef={dialogRef}
token={data.token ?? ""}
onDone={() => {
dialogRef.current?.close();
}}
/>
</>
);
}

View File

@@ -7,7 +7,6 @@ import {
} from "@probo/ui"; } from "@probo/ui";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { useCopy } from "@probo/hooks"; import { useCopy } from "@probo/hooks";
import { useEffect } from "react";
export function PersonalAPIKeyTokenDialog(props: { export function PersonalAPIKeyTokenDialog(props: {
dialogRef: React.RefObject<{ open: () => void; close: () => void } | null>; dialogRef: React.RefObject<{ open: () => void; close: () => void } | null>;
@@ -18,8 +17,6 @@ export function PersonalAPIKeyTokenDialog(props: {
const { __ } = useTranslate(); const { __ } = useTranslate();
const [isCopied, copy] = useCopy(); const [isCopied, copy] = useCopy();
useEffect(() => {}, [token]);
return ( return (
<Dialog <Dialog
ref={dialogRef} ref={dialogRef}

View File

@@ -1,24 +1,14 @@
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { formatDate } from "@probo/helpers"; import { Table, Tbody, Th, Thead, Tr } from "@probo/ui";
import { Button, Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui"; import { PersonalAPIKeyRow } from "./PersonalAPIKeyRow";
import type { PersonalAPIKeyListFragment$data } from "/__generated__/iam/PersonalAPIKeyListFragment.graphql";
export type PersonalAPIKeyRow = {
id: string;
name: string;
createdAt: string;
expiresAt: string;
lastUsedAt: string | null;
};
export function PersonalAPIKeysTable(props: { export function PersonalAPIKeysTable(props: {
keys: PersonalAPIKeyRow[]; edges: PersonalAPIKeyListFragment$data["personalAPIKeys"]["edges"];
onRevoke: (key: { id: string; name: string }) => void; connectionId: string;
onShowToken: (key: { id: string; name: string }) => void;
isShowingToken?: boolean;
}) { }) {
const { keys, onRevoke, onShowToken, isShowingToken } = props; const { edges, connectionId } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
const now = new Date();
return ( return (
<Table> <Table>
@@ -32,51 +22,13 @@ export function PersonalAPIKeysTable(props: {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{keys.map((k) => { {edges.map(({ node }) => (
const expired = new Date(k.expiresAt) < now; <PersonalAPIKeyRow
return ( key={node.id}
<Tr key={k.id}> fKey={node}
<Td> connectionId={connectionId}
<div className="font-medium text-txt-primary">{k.name}</div> />
<div className="text-xs text-txt-tertiary"> ))}
{expired ? __("Expired") : __("Active")}
</div>
</Td>
<Td>
<span className="text-sm text-txt-secondary">
{k.lastUsedAt ? formatDate(k.lastUsedAt) : "—"}
</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary">
{formatDate(k.createdAt)}
</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary">
{formatDate(k.expiresAt)}
</span>
</Td>
<Td width={140} className="text-end">
<div className="flex gap-2 justify-end">
<Button
variant="secondary"
onClick={() => onShowToken({ id: k.id, name: k.name })}
disabled={!!isShowingToken}
>
{__("Show")}
</Button>
<Button
variant="danger"
onClick={() => onRevoke({ id: k.id, name: k.name })}
>
{__("Revoke")}
</Button>
</div>
</Td>
</Tr>
);
})}
</Tbody> </Tbody>
</Table> </Table>
); );

View File

@@ -56,6 +56,21 @@ func (a *Asset) CursorKey(field AssetOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (a *Asset) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM assets WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, a.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query asset authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (a *Asset) LoadByID( func (a *Asset) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -60,6 +60,21 @@ func (a *Audit) CursorKey(field AuditOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (a *Audit) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM audits WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, a.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query audit authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (a *Audit) LoadByID( func (a *Audit) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -17,16 +17,17 @@ package coredata
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/connector" "go.probo.inc/probo/pkg/connector"
"go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/crypto/cipher"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
) )
type ( type (
@@ -56,6 +57,21 @@ func (c *Connector) CursorKey(orderBy ConnectorOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (c *Connector) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM connectors WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, c.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query connector authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (c *Connectors) LoadByOrganizationID( func (c *Connectors) LoadByOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,14 +16,15 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
) )
type ( type (
@@ -63,6 +64,21 @@ func (ci *ContinualImprovement) CursorKey(field ContinualImprovementOrderField)
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (ci *ContinualImprovement) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM continual_improvements WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, ci.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query continual improvement authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (ci *ContinualImprovement) LoadByID( func (ci *ContinualImprovement) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -57,6 +57,21 @@ func (c Control) CursorKey(orderBy ControlOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (c *Control) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM controls WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, c.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query control authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (c *Controls) CountByDocumentID( func (c *Controls) CountByDocumentID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -65,6 +65,21 @@ func NewCustomDomain(tenantID gid.TenantID, domain string) *CustomDomain {
} }
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (cd *CustomDomain) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM custom_domains WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, cd.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query custom domain authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (cd *CustomDomain) CursorKey(field CustomDomainOrderField) page.CursorKey { func (cd *CustomDomain) CursorKey(field CustomDomainOrderField) page.CursorKey {
switch field { switch field {
case CustomDomainOrderFieldCreatedAt: case CustomDomainOrderFieldCreatedAt:

View File

@@ -69,6 +69,21 @@ func (dpia *DataProtectionImpactAssessment) CursorKey(field DataProtectionImpact
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (dpia *DataProtectionImpactAssessment) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM processing_activity_data_protection_impact_assessments WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, dpia.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query data protection impact assessment authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (dpias *DataProtectionImpactAssessments) CountByOrganizationID( func (dpias *DataProtectionImpactAssessments) CountByOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,14 +16,15 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
) )
type ( type (
@@ -59,6 +60,21 @@ func (d *Datum) CursorKey(field DatumOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (d *Datum) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM data WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, d.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query datum authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (d *Datum) LoadByID( func (d *Datum) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -58,6 +58,21 @@ func (p Document) CursorKey(orderBy DocumentOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (d *Document) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM documents WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, d.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query document authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (p *Document) LoadByID( func (p *Document) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -48,6 +48,21 @@ type (
DocumentVersions []*DocumentVersion DocumentVersions []*DocumentVersion
) )
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (dv *DocumentVersion) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM document_versions WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, dv.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query document version authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (p *DocumentVersions) LoadByDocumentID( func (p *DocumentVersions) LoadByDocumentID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -62,6 +62,21 @@ func (pvs DocumentVersionSignature) CursorKey(orderBy DocumentVersionSignatureOr
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (dvs *DocumentVersionSignature) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM document_version_signatures WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, dvs.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query document version signature authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (pvs *DocumentVersionSignature) LoadByDocumentVersionIDAndSignatory( func (pvs *DocumentVersionSignature) LoadByDocumentVersionIDAndSignatory(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -44,6 +44,12 @@ var (
ErrNoUnsentEmail = errors.New("no unsent email found") ErrNoUnsentEmail = errors.New("no unsent email found")
) )
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
// Email is identity-scoped (not org-scoped), so it returns an empty map.
func (e *Email) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
return map[string]string{}, nil
}
func NewEmail( func NewEmail(
recipientName string, recipientName string,
recipientEmail mail.Addr, recipientEmail mail.Addr,

View File

@@ -14,6 +14,8 @@
package coredata package coredata
import "go.probo.inc/probo/pkg/gid"
type ctxKey struct{ name string } type ctxKey struct{ name string }
var ( var (
@@ -75,6 +77,109 @@ const (
MembershipProfileEntityType uint16 = 51 MembershipProfileEntityType uint16 = 51
) )
func NewEntityFromID(id gid.GID) (any, bool) {
switch id.EntityType() {
case OrganizationEntityType:
return &Organization{ID: id}, true
case FrameworkEntityType:
return &Framework{ID: id}, true
case MeasureEntityType:
return &Measure{ID: id}, true
case TaskEntityType:
return &Task{ID: id}, true
case EvidenceEntityType:
return &Evidence{ID: id}, true
case ConnectorEntityType:
return &Connector{ID: id}, true
case VendorRiskAssessmentEntityType:
return &VendorRiskAssessment{ID: id}, true
case VendorEntityType:
return &Vendor{ID: id}, true
case PeopleEntityType:
return &People{ID: id}, true
case VendorComplianceReportEntityType:
return &VendorComplianceReport{ID: id}, true
case DocumentEntityType:
return &Document{ID: id}, true
case IdentityEntityType:
return &Identity{ID: id}, true
case SessionEntityType:
return &Session{ID: id}, true
case EmailEntityType:
return &Email{ID: id}, true
case ControlEntityType:
return &Control{ID: id}, true
case RiskEntityType:
return &Risk{ID: id}, true
case DocumentVersionEntityType:
return &DocumentVersion{ID: id}, true
case DocumentVersionSignatureEntityType:
return &DocumentVersionSignature{ID: id}, true
case AssetEntityType:
return &Asset{ID: id}, true
case DatumEntityType:
return &Datum{ID: id}, true
case AuditEntityType:
return &Audit{ID: id}, true
case ReportEntityType:
return &Report{ID: id}, true
case TrustCenterEntityType:
return &TrustCenter{ID: id}, true
case TrustCenterAccessEntityType:
return &TrustCenterAccess{ID: id}, true
case VendorBusinessAssociateAgreementEntityType:
return &VendorBusinessAssociateAgreement{ID: id}, true
case FileEntityType:
return &File{ID: id}, true
case VendorContactEntityType:
return &VendorContact{ID: id}, true
case VendorDataPrivacyAgreementEntityType:
return &VendorDataPrivacyAgreement{ID: id}, true
case NonconformityEntityType:
return &Nonconformity{ID: id}, true
case ObligationEntityType:
return &Obligation{ID: id}, true
case VendorServiceEntityType:
return &VendorService{ID: id}, true
case SnapshotEntityType:
return &Snapshot{ID: id}, true
case ContinualImprovementEntityType:
return &ContinualImprovement{ID: id}, true
case ProcessingActivityEntityType:
return &ProcessingActivity{ID: id}, true
case ExportJobEntityType:
return &ExportJob{ID: id}, true
case TrustCenterReferenceEntityType:
return &TrustCenterReference{ID: id}, true
case TrustCenterDocumentAccessEntityType:
return &TrustCenterDocumentAccess{ID: id}, true
case CustomDomainEntityType:
return &CustomDomain{ID: id}, true
case InvitationEntityType:
return &Invitation{ID: id}, true
case MembershipEntityType:
return &Membership{ID: id}, true
case SlackMessageEntityType:
return &SlackMessage{ID: id}, true
case TrustCenterFileEntityType:
return &TrustCenterFile{ID: id}, true
case SAMLConfigurationEntityType:
return &SAMLConfiguration{ID: id}, true
case PersonalAPIKeyEntityType:
return &PersonalAPIKey{ID: id}, true
case MeetingEntityType:
return &Meeting{ID: id}, true
case DataProtectionImpactAssessmentEntityType:
return &DataProtectionImpactAssessment{ID: id}, true
case TransferImpactAssessmentEntityType:
return &TransferImpactAssessment{ID: id}, true
case MembershipProfileEntityType:
return &MembershipProfile{ID: id}, true
default:
return nil, false
}
}
type EntityInfo struct { type EntityInfo struct {
Model string Model string
Table string Table string

View File

@@ -56,6 +56,21 @@ func (e Evidence) CursorKey(orderBy EvidenceOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (e *Evidence) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM evidences WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, e.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query evidence authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (e Evidence) Upsert( func (e Evidence) Upsert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,18 +16,18 @@ import (
type ( type (
ExportJob struct { ExportJob struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
Type ExportJobType `db:"type"` Type ExportJobType `db:"type"`
Arguments json.RawMessage `db:"arguments"` Arguments json.RawMessage `db:"arguments"`
Error *string `db:"error"` Error *string `db:"error"`
Status ExportJobStatus `db:"status"` Status ExportJobStatus `db:"status"`
FileID *gid.GID `db:"file_id"` FileID *gid.GID `db:"file_id"`
RecipientEmail mail.Addr `db:"recipient_email"` RecipientEmail mail.Addr `db:"recipient_email"`
RecipientName string `db:"recipient_name"` RecipientName string `db:"recipient_name"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
StartedAt *time.Time `db:"started_at"` StartedAt *time.Time `db:"started_at"`
CompletedAt *time.Time `db:"completed_at"` CompletedAt *time.Time `db:"completed_at"`
} }
ExportJobs []*ExportJob ExportJobs []*ExportJob
@@ -48,6 +48,21 @@ var (
ErrNoExportJobAvailable = errors.New("no export job available") ErrNoExportJobAvailable = errors.New("no export job available")
) )
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (ej *ExportJob) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM export_jobs WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, ej.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query export job authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (ej *ExportJob) Insert( func (ej *ExportJob) Insert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -44,6 +44,21 @@ type (
Files []*File Files []*File
) )
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (f *File) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM files WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, f.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query file authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (f *File) LoadByID( func (f *File) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -53,6 +53,21 @@ func (f *Framework) CursorKey(orderBy FrameworkOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (f *Framework) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM frameworks WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, f.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query framework authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (f *Frameworks) CountByOrganizationID( func (f *Frameworks) CountByOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -220,6 +220,34 @@ LIMIT 1;
return nil return nil
} }
// AuthorizationAttributes loads the minimal authorization attributes for policy condition evaluation.
// It is intentionally lightweight and does not populate the Identity struct.
func (i *Identity) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `
SELECT
id,
email_address
FROM
identities
WHERE
id = $1
LIMIT 1;
`
var (
id gid.GID
emailAddress string
)
if err := conn.QueryRow(ctx, q, i.ID).Scan(&id, &emailAddress); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query identity iam attributes: %w", err)
}
return map[string]string{"id": id.String(), "email": emailAddress}, nil
}
func (i *Identity) Insert( func (i *Identity) Insert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -164,6 +164,35 @@ WHERE
return nil return nil
} }
// AuthorizationAttributes loads the minimal authorization attributes for policy condition evaluation.
// It is intentionally lightweight and does not populate the Invitation struct.
func (i *Invitation) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `
SELECT
email
, organization_id
FROM
iam_invitations
WHERE
id = $1
LIMIT 1;
`
var email string
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, i.ID).Scan(&email, &organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query invitation iam attributes: %w", err)
}
return map[string]string{
"email": email,
"organization_id": organizationID.String(),
}, nil
}
func (i *Invitation) Update(ctx context.Context, conn pg.Conn, scope Scoper) error { func (i *Invitation) Update(ctx context.Context, conn pg.Conn, scope Scoper) error {
query := ` query := `
UPDATE UPDATE

View File

@@ -56,6 +56,21 @@ func (m Measure) CursorKey(orderBy MeasureOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (m *Measure) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM measures WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, m.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query measure authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (m *Measures) CountByRiskID( func (m *Measures) CountByRiskID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -54,6 +54,21 @@ func (m Meeting) CursorKey(orderBy MeetingOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
// AuthorizationAttributes returns the authorization attributes for policy evaluation.
func (m *Meeting) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM meetings WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, m.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query meeting authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (m *Meeting) LoadByID( func (m *Meeting) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -226,6 +226,33 @@ LEFT JOIN
return nil return nil
} }
func (m *Membership) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `
SELECT
identity_id
, organization_id
FROM
iam_memberships
WHERE
id = $1
LIMIT 1;
`
var identityID gid.GID
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, m.ID).Scan(&identityID, &organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query membership iam attributes: %w", err)
}
return map[string]string{
"identity_id": identityID.String(),
"organization_id": organizationID.String(),
}, nil
}
func LoadRoleByIdentityAndEntityIDOnly( func LoadRoleByIdentityAndEntityIDOnly(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
@@ -235,7 +262,6 @@ func LoadRoleByIdentityAndEntityIDOnly(
) (MembershipRole, error) { ) (MembershipRole, error) {
entityType := entityID.EntityType() entityType := entityID.EntityType()
// For organization, the entity ID is the organization ID - optimized path
if entityType == OrganizationEntityType { if entityType == OrganizationEntityType {
query := ` query := `
SELECT role SELECT role
@@ -595,3 +621,39 @@ WHERE
return count, nil return count, nil
} }
func (m *Memberships) LoadAllByIdentityID(
ctx context.Context,
conn pg.Conn,
identityID gid.GID,
) error {
q := `
SELECT
id,
identity_id,
organization_id,
role,
'' as full_name,
NULL as email_address,
created_at,
updated_at
FROM
iam_memberships
WHERE
identity_id = $1
;
`
rows, err := conn.Query(ctx, q, identityID)
if err != nil {
return fmt.Errorf("cannot query memberships: %w", err)
}
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Membership])
if err != nil {
return fmt.Errorf("cannot collect memberships: %w", err)
}
*m = memberships
return nil
}

View File

@@ -36,6 +36,20 @@ type (
} }
) )
func (p *MembershipProfile) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT m.organization_id FROM iam_membership_profiles mp JOIN iam_memberships m ON mp.membership_id = m.id WHERE mp.id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, p.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query membership profile authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (p *MembershipProfile) LoadByMembershipID( func (p *MembershipProfile) LoadByMembershipID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,14 +16,15 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
) )
type ( type (
@@ -66,6 +67,20 @@ func (nc *Nonconformity) CursorKey(field NonconformityOrderField) page.CursorKey
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
func (nc *Nonconformity) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM nonconformities WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, nc.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query nonconformity authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (nc *Nonconformity) LoadByID( func (nc *Nonconformity) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
@@ -399,9 +414,9 @@ WHERE %s AND nc.organization_id = @organization_id AND nc.snapshot_id IS NULL
query = fmt.Sprintf(query, scope.SQLFragment()) query = fmt.Sprintf(query, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(), "tenant_id": scope.GetTenantID(),
"snapshot_id": snapshotID, "snapshot_id": snapshotID,
"organization_id": organizationID, "organization_id": organizationID,
"nonconformity_entity_type": NonconformityEntityType, "nonconformity_entity_type": NonconformityEntityType,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())

View File

@@ -16,6 +16,7 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -64,6 +65,20 @@ func (o *Obligation) CursorKey(field ObligationOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
func (o *Obligation) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM obligations WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, o.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query obligation authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (o *Obligation) LoadByID( func (o *Obligation) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -46,6 +46,20 @@ type (
Organizations []*Organization Organizations []*Organization
) )
func (o *Organization) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT id FROM organizations WHERE id = $1 LIMIT 1;`
var id gid.GID
if err := conn.QueryRow(ctx, q, o.ID).Scan(&id); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query organization authorization attributes: %w", err)
}
return map[string]string{"organization_id": o.ID.String()}, nil
}
func (o Organization) CursorKey(orderBy OrganizationOrderField) page.CursorKey { func (o Organization) CursorKey(orderBy OrganizationOrderField) page.CursorKey {
switch orderBy { switch orderBy {
case OrganizationOrderFieldName: case OrganizationOrderFieldName:

View File

@@ -60,6 +60,20 @@ func (p People) CursorKey(orderBy PeopleOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (p *People) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM peoples WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, p.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query people authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (p *People) LoadByID( func (p *People) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -91,6 +91,20 @@ LIMIT 1;
return nil return nil
} }
func (a *PersonalAPIKey) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := "SELECT identity_id FROM iam_personal_api_keys WHERE id = $1 LIMIT 1;"
var identityID gid.GID
if err := conn.QueryRow(ctx, q, a.ID).Scan(&identityID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query personal api key iam attributes: %w", err)
}
return map[string]string{"identity_id": identityID.String()}, nil
}
func (a *PersonalAPIKeys) LoadByIdentityID( func (a *PersonalAPIKeys) LoadByIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,6 +16,7 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -75,6 +76,20 @@ func (p *ProcessingActivity) CursorKey(field ProcessingActivityOrderField) page.
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
func (p *ProcessingActivity) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM processing_activities WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, p.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query processing activity authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (p *ProcessingActivity) LoadByID( func (p *ProcessingActivity) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -42,6 +42,20 @@ type (
Reports []*Report Reports []*Report
) )
func (r *Report) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM reports WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, r.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query report authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (r *Report) LoadByID( func (r *Report) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -80,6 +80,20 @@ func (r *Risk) CursorKey(orderBy RiskOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (r *Risk) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM risks WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, r.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query risk authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (r *Risks) CountByMeasureID( func (r *Risks) CountByMeasureID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -63,6 +63,20 @@ func (s *SAMLConfiguration) CursorKey(orderBy SAMLConfigurationOrderField) page.
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (s *SAMLConfiguration) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM iam_saml_configurations WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, s.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query saml configuration authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (s *SAMLConfiguration) GetIdPCertificate() (*x509.Certificate, error) { func (s *SAMLConfiguration) GetIdPCertificate() (*x509.Certificate, error) {
block, _ := pem.Decode([]byte(s.IdPCertificate)) block, _ := pem.Decode([]byte(s.IdPCertificate))
if block == nil { if block == nil {

View File

@@ -140,6 +140,30 @@ LIMIT 1;
return nil return nil
} }
// AuthorizationAttributes loads the minimal authorization attributes for policy condition evaluation.
// It is intentionally lightweight and does not populate the Session struct.
func (s *Session) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `
SELECT
identity_id
FROM
iam_sessions
WHERE
id = $1
LIMIT 1;
`
var identityID gid.GID
if err := conn.QueryRow(ctx, q, s.ID).Scan(&identityID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query session iam attributes: %w", err)
}
return map[string]string{"identity_id": identityID.String()}, nil
}
func (s *Session) Insert( func (s *Session) Insert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -29,19 +29,19 @@ import (
type ( type (
SlackMessage struct { SlackMessage struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
Type SlackMessageType `db:"type"` Type SlackMessageType `db:"type"`
Body map[string]any `db:"body"` Body map[string]any `db:"body"`
MessageTS *string `db:"message_ts"` MessageTS *string `db:"message_ts"`
ChannelID *string `db:"channel_id"` ChannelID *string `db:"channel_id"`
RequesterEmail *mail.Addr `db:"requester_email"` RequesterEmail *mail.Addr `db:"requester_email"`
Metadata map[string]any `db:"metadata"` Metadata map[string]any `db:"metadata"`
InitialSlackMessageID gid.GID `db:"initial_slack_message_id"` InitialSlackMessageID gid.GID `db:"initial_slack_message_id"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
SentAt *time.Time `db:"sent_at"` SentAt *time.Time `db:"sent_at"`
Error *string `db:"error"` Error *string `db:"error"`
} }
ErrNoUnsentSlackMessage struct{} ErrNoUnsentSlackMessage struct{}
@@ -57,6 +57,20 @@ func (e ErrSlackMessageNotFound) Error() string {
return "slack message not found" return "slack message not found"
} }
func (sm *SlackMessage) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM slack_messages WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, sm.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query slack message authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func NewSlackMessage( func NewSlackMessage(
scope Scoper, scope Scoper,
organizationID gid.GID, organizationID gid.GID,

View File

@@ -16,6 +16,7 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -52,6 +53,20 @@ func (s *Snapshot) CursorKey(field SnapshotOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
func (s *Snapshot) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM snapshots WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, s.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query snapshot authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (s *Snapshot) LoadByID( func (s *Snapshot) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -57,6 +57,20 @@ func (c Task) CursorKey(orderBy TaskOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (t *Task) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM tasks WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, t.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query task authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (c *Task) LoadByID( func (c *Task) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -69,6 +69,20 @@ func (tia *TransferImpactAssessment) CursorKey(field TransferImpactAssessmentOrd
panic(fmt.Sprintf("unsupported order by: %s", field)) panic(fmt.Sprintf("unsupported order by: %s", field))
} }
func (tia *TransferImpactAssessment) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM processing_activity_transfer_impact_assessments WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, tia.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query transfer impact assessment authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (tias *TransferImpactAssessments) CountByOrganizationID( func (tias *TransferImpactAssessments) CountByOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -52,6 +52,20 @@ func (tc *TrustCenter) CursorKey(orderBy TrustCenterOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (tc *TrustCenter) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM trust_centers WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, tc.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query trust center authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (tc *TrustCenter) LoadByID( func (tc *TrustCenter) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -32,19 +32,19 @@ import (
type ( type (
TrustCenterAccess struct { TrustCenterAccess struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
TenantID gid.TenantID `db:"tenant_id"` TenantID gid.TenantID `db:"tenant_id"`
TrustCenterID gid.GID `db:"trust_center_id"` TrustCenterID gid.GID `db:"trust_center_id"`
Email mail.Addr `db:"email"` Email mail.Addr `db:"email"`
Name string `db:"name"` Name string `db:"name"`
Active bool `db:"active"` Active bool `db:"active"`
HasAcceptedNonDisclosureAgreement bool `db:"has_accepted_non_disclosure_agreement"` HasAcceptedNonDisclosureAgreement bool `db:"has_accepted_non_disclosure_agreement"`
HasAcceptedNonDisclosureAgreementMetadata json.RawMessage `db:"has_accepted_non_disclosure_agreement_metadata"` HasAcceptedNonDisclosureAgreementMetadata json.RawMessage `db:"has_accepted_non_disclosure_agreement_metadata"`
NDAFileID *gid.GID `db:"nda_file_id"` NDAFileID *gid.GID `db:"nda_file_id"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
LastTokenExpiresAt *time.Time `db:"last_token_expires_at"` LastTokenExpiresAt *time.Time `db:"last_token_expires_at"`
} }
TrustCenterAccesses []*TrustCenterAccess TrustCenterAccesses []*TrustCenterAccess
@@ -59,6 +59,20 @@ func (tca *TrustCenterAccess) CursorKey(orderBy TrustCenterAccessOrderField) pag
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (tca *TrustCenterAccess) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM trust_center_accesses WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, tca.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query trust center access authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (tca *TrustCenterAccess) LoadByID( func (tca *TrustCenterAccess) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -53,6 +53,20 @@ func (tcda *TrustCenterDocumentAccess) CursorKey(orderBy TrustCenterDocumentAcce
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (tcda *TrustCenterDocumentAccess) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM trust_center_document_accesses WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, tcda.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query trust center document access authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (tcda *TrustCenterDocumentAccess) LoadByID( func (tcda *TrustCenterDocumentAccess) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,6 +16,7 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -53,6 +54,20 @@ func (t TrustCenterFile) CursorKey(orderBy TrustCenterFileOrderField) page.Curso
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (t *TrustCenterFile) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM trust_center_files WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, t.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query trust center file authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (t *TrustCenterFile) LoadByID( func (t *TrustCenterFile) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -59,6 +59,20 @@ func (t TrustCenterReference) CursorKey(orderBy TrustCenterReferenceOrderField)
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (t *TrustCenterReference) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM trust_center_references WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, t.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query trust center reference authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (t *TrustCenterReference) LoadByID( func (t *TrustCenterReference) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -78,6 +78,20 @@ func (v Vendor) CursorKey(orderBy VendorOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (v *Vendor) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendors WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, v.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (v *Vendor) LoadByID( func (v *Vendor) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -56,6 +56,20 @@ func (v VendorBusinessAssociateAgreement) CursorKey(orderBy VendorBusinessAssoci
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (vbaa *VendorBusinessAssociateAgreement) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendor_business_associate_agreements WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, vbaa.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor business associate agreement authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (vbaa *VendorBusinessAssociateAgreement) LoadByVendorID( func (vbaa *VendorBusinessAssociateAgreement) LoadByVendorID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,14 +16,15 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
) )
type ( type (
@@ -55,6 +56,20 @@ func (c VendorComplianceReport) CursorKey(orderBy VendorComplianceReportOrderFie
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (v *VendorComplianceReport) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendor_compliance_reports WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, v.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor compliance report authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (vcs *VendorComplianceReports) LoadForVendorID( func (vcs *VendorComplianceReports) LoadForVendorID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -59,6 +59,20 @@ func (vc VendorContact) CursorKey(orderBy VendorContactOrderField) page.CursorKe
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (vc *VendorContact) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendor_contacts WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, vc.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor contact authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (vc *VendorContact) LoadByID( func (vc *VendorContact) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -56,6 +56,20 @@ func (v VendorDataPrivacyAgreement) CursorKey(orderBy VendorDataPrivacyAgreement
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (vdpa *VendorDataPrivacyAgreement) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendor_data_privacy_agreements WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, vdpa.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor data privacy agreement authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (vdpa *VendorDataPrivacyAgreement) LoadByVendorID( func (vdpa *VendorDataPrivacyAgreement) LoadByVendorID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,6 +16,7 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -56,6 +57,20 @@ func (v VendorRiskAssessment) CursorKey(orderBy VendorRiskAssessmentOrderField)
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (v *VendorRiskAssessment) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendor_risk_assessments WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, v.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor risk assessment authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
// Insert adds a new risk assessment to the database // Insert adds a new risk assessment to the database
func (r VendorRiskAssessment) Insert( func (r VendorRiskAssessment) Insert(
ctx context.Context, ctx context.Context,

View File

@@ -54,6 +54,20 @@ func (vs VendorService) CursorKey(orderBy VendorServiceOrderField) page.CursorKe
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (vs *VendorService) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
q := `SELECT organization_id FROM vendor_services WHERE id = $1 LIMIT 1;`
var organizationID gid.GID
if err := conn.QueryRow(ctx, q, vs.ID).Scan(&organizationID); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrResourceNotFound
}
return nil, fmt.Errorf("cannot query vendor service authorization attributes: %w", err)
}
return map[string]string{"organization_id": organizationID.String()}, nil
}
func (vs *VendorService) LoadByID( func (vs *VendorService) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -16,7 +16,7 @@ package iam
import ( import (
"context" "context"
"errors" "fmt"
"maps" "maps"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
@@ -25,12 +25,28 @@ import (
"go.probo.inc/probo/pkg/iam/policy" "go.probo.inc/probo/pkg/iam/policy"
) )
// AuthorizationAttributer is implemented by entities that provide attributes
// for policy condition evaluation.
type AuthorizationAttributer interface {
AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error)
}
// AuthorizeParams contains the parameters for an authorization request.
type AuthorizeParams struct {
Principal gid.GID
Resource gid.GID
Action string
ResourceAttributes map[string]string
}
// Authorizer evaluates authorization requests against registered policies.
type Authorizer struct { type Authorizer struct {
pg *pg.Client pg *pg.Client
evaluator *policy.Evaluator evaluator *policy.Evaluator
policySet *PolicySet policySet *PolicySet
} }
// NewAuthorizer creates a new Authorizer instance.
func NewAuthorizer(pgClient *pg.Client) *Authorizer { func NewAuthorizer(pgClient *pg.Client) *Authorizer {
return &Authorizer{ return &Authorizer{
pg: pgClient, pg: pgClient,
@@ -39,87 +55,144 @@ func NewAuthorizer(pgClient *pg.Client) *Authorizer {
} }
} }
func (a *Authorizer) RegisterPolicySet(policySet *PolicySet) { // RegisterPolicySet merges the given policy set into the authorizer.
a.policySet.Merge(policySet) func (a *Authorizer) RegisterPolicySet(ps *PolicySet) {
} a.policySet.Merge(ps)
type AuthorizeParams struct {
Principal gid.GID
Resource gid.GID
Action string
ResourceAttributes map[string]string
} }
// Authorize checks if the principal is allowed to perform the action on the resource.
func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error { func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error {
if params.Principal.EntityType() != coredata.IdentityEntityType { if params.Principal.EntityType() != coredata.IdentityEntityType {
return NewUnsupportedPrincipalTypeError(params.Principal.EntityType()) return NewUnsupportedPrincipalTypeError(params.Principal.EntityType())
} }
policies := a.buildPolicies(ctx, params) return a.pg.WithConn(ctx, func(conn pg.Conn) error { return a.authorize(ctx, conn, params) })
}
// Pre-allocate Resource map with capacity for id + attributes func (a *Authorizer) authorize(ctx context.Context, conn pg.Conn, params AuthorizeParams) error {
resourceAttrs := make(map[string]string, 1+len(params.ResourceAttributes)) memberships, err := a.loadMemberships(ctx, conn, params.Principal)
resourceAttrs["id"] = params.Resource.String() if err != nil {
maps.Copy(resourceAttrs, params.ResourceAttributes) return err
conditionCtx := policy.ConditionContext{
Principal: map[string]string{
"id": params.Principal.String(),
},
Resource: resourceAttrs,
} }
resourceAttrs, err := a.buildResourceAttributes(ctx, conn, params)
if err != nil {
return err
}
// Find role for resource's organization
resourceOrgID := resourceAttrs["organization_id"]
role := findRoleForOrg(memberships, resourceOrgID)
// Only set principal.organization_id if they have a role in this org
var principalOrgID string
if role != "" {
principalOrgID = resourceOrgID
}
principalAttrs, err := a.buildPrincipalAttributes(ctx, conn, params.Principal, principalOrgID)
if err != nil {
return err
}
policies := a.buildPoliciesForRole(role)
req := policy.AuthorizationRequest{ req := policy.AuthorizationRequest{
Principal: params.Principal, Principal: params.Principal,
Resource: params.Resource, Resource: params.Resource,
Action: params.Action, Action: params.Action,
ConditionContext: conditionCtx, ConditionContext: policy.ConditionContext{
Principal: principalAttrs,
Resource: resourceAttrs,
},
} }
result := a.evaluator.Evaluate(req, policies) if a.evaluator.Evaluate(req, policies).IsAllowed() {
if result.IsAllowed() {
return nil return nil
} }
return NewInsufficientPermissionsError(params.Principal, params.Resource, params.Action) return NewInsufficientPermissionsError(params.Principal, params.Resource, params.Action)
} }
func (a *Authorizer) buildPolicies(ctx context.Context, params AuthorizeParams) []*policy.Policy { func (a *Authorizer) loadMemberships(ctx context.Context, conn pg.Conn, principalID gid.GID) (coredata.Memberships, error) {
selfManageCount := len(a.policySet.SelfManagePolicies) var memberships coredata.Memberships
if err := memberships.LoadAllByIdentityID(ctx, conn, principalID); err != nil {
return nil, fmt.Errorf("cannot load memberships: %w", err)
}
return memberships, nil
}
var rolePolicies []*policy.Policy func (a *Authorizer) buildPrincipalAttributes(
if params.Resource.TenantID() != gid.NilTenant { ctx context.Context,
rolePolicies = a.loadRolePolicies(ctx, params.Principal, params.Resource) conn pg.Conn,
principalID gid.GID,
organizationID string,
) (map[string]string, error) {
attrs := map[string]string{
"id": principalID.String(),
"organization_id": organizationID,
} }
totalCount := selfManageCount + len(rolePolicies) if entity, ok := coredata.NewEntityFromID(principalID); ok {
policies := make([]*policy.Policy, selfManageCount, totalCount) if attributer, ok := entity.(AuthorizationAttributer); ok {
copy(policies, a.policySet.SelfManagePolicies) entityAttrs, err := attributer.AuthorizationAttributes(ctx, conn)
policies = append(policies, rolePolicies...) if err != nil {
return nil, fmt.Errorf("cannot load principal attributes: %w", err)
}
maps.Copy(attrs, entityAttrs)
}
}
return attrs, nil
}
func (a *Authorizer) buildResourceAttributes(
ctx context.Context,
conn pg.Conn,
params AuthorizeParams,
) (map[string]string, error) {
attrs := map[string]string{
"id": params.Resource.String(),
}
entity, ok := coredata.NewEntityFromID(params.Resource)
if !ok {
return nil, fmt.Errorf("unsupported resource type: %d", params.Resource.EntityType())
}
attributer, ok := entity.(AuthorizationAttributer)
if !ok {
return nil, fmt.Errorf("resource %d does not implement AuthorizationAttributer", params.Resource.EntityType())
}
entityAttrs, err := attributer.AuthorizationAttributes(ctx, conn)
if err != nil {
return nil, fmt.Errorf("cannot load resource attributes: %w", err)
}
maps.Copy(attrs, entityAttrs)
if params.ResourceAttributes != nil {
maps.Copy(attrs, params.ResourceAttributes)
}
return attrs, nil
}
func (a *Authorizer) buildPoliciesForRole(role string) []*policy.Policy {
policies := append([]*policy.Policy{}, a.policySet.IdentityScopedPolicies...)
if role != "" {
policies = append(policies, a.policySet.RolePolicies[role]...)
}
return policies return policies
} }
func (a *Authorizer) loadRolePolicies(ctx context.Context, principalID gid.GID, resourceID gid.GID) []*policy.Policy { func findRoleForOrg(memberships coredata.Memberships, orgID string) string {
var role coredata.MembershipRole for _, m := range memberships {
if m.OrganizationID.String() == orgID {
err := a.pg.WithConn( return string(m.Role)
ctx, }
func(conn pg.Conn) (err error) {
scope := coredata.NewScopeFromObjectID(resourceID)
role, err = coredata.LoadRoleByIdentityAndEntityIDOnly(ctx, conn, scope, principalID, resourceID)
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil // No membership = no role-based policies
}
return err
},
)
if err != nil || role == "" {
return nil
} }
return ""
return a.policySet.RolePolicies[role.String()]
} }

View File

@@ -54,7 +54,7 @@ var IAMSelfManageSessionPolicy = policy.NewPolicy(
ActionIAMSessionRevoke, ActionIAMSessionRevoke,
ActionIAMSessionRevokeAll, ActionIAMSessionRevokeAll,
).WithSID("manage-own-sessions"). ).WithSID("manage-own-sessions").
When(policy.Equals("principal.id", "resource.user_id")), When(policy.Equals("principal.id", "resource.identity_id")),
).WithDescription("Allows users to view and revoke their own sessions") ).WithDescription("Allows users to view and revoke their own sessions")
// IAMSelfManageInvitationPolicy allows users to manage invitations sent to them. // IAMSelfManageInvitationPolicy allows users to manage invitations sent to them.
@@ -66,7 +66,7 @@ var IAMSelfManageInvitationPolicy = policy.NewPolicy(
ActionIAMInvitationGet, ActionIAMInvitationGet,
ActionIAMInvitationAccept, ActionIAMInvitationAccept,
).WithSID("manage-own-invitations"). ).WithSID("manage-own-invitations").
When(policy.Equals("principal.id", "resource.user_id")), When(policy.Equals("principal.email", "resource.email")),
).WithDescription("Allows users to view and accept invitations sent to them") ).WithDescription("Allows users to view and accept invitations sent to them")
// IAMSelfManageMembershipPolicy allows users to view their own memberships. // IAMSelfManageMembershipPolicy allows users to view their own memberships.
@@ -77,7 +77,7 @@ var IAMSelfManageMembershipPolicy = policy.NewPolicy(
policy.Allow( policy.Allow(
ActionIAMMembershipGet, ActionIAMMembershipGet,
).WithSID("view-own-memberships"). ).WithSID("view-own-memberships").
When(policy.Equals("principal.id", "resource.user_id")), When(policy.Equals("principal.id", "resource.identity_id")),
).WithDescription("Allows users to view their organization memberships") ).WithDescription("Allows users to view their organization memberships")
// IAMSelfManagePersonalAPIKeyPolicy allows users to manage their own API keys. // IAMSelfManagePersonalAPIKeyPolicy allows users to manage their own API keys.
@@ -91,7 +91,7 @@ var IAMSelfManagePersonalAPIKeyPolicy = policy.NewPolicy(
ActionIAMPersonalAPIKeyUpdate, ActionIAMPersonalAPIKeyUpdate,
ActionIAMPersonalAPIKeyDelete, ActionIAMPersonalAPIKeyDelete,
).WithSID("manage-own-api-keys"). ).WithSID("manage-own-api-keys").
When(policy.Equals("principal.id", "resource.user_id")), When(policy.Equals("principal.id", "resource.identity_id")),
).WithDescription("Allows users to manage their own personal API keys") ).WithDescription("Allows users to manage their own personal API keys")
// IAMOwnerPolicy defines permissions for organization owners. // IAMOwnerPolicy defines permissions for organization owners.
@@ -99,17 +99,20 @@ var IAMOwnerPolicy = policy.NewPolicy(
"iam:owner", "iam:owner",
"Organization Owner", "Organization Owner",
// Full access to organization management // Full access to organization management
policy.Allow("iam:organization:*").WithSID("full-org-access"), policy.Allow("iam:organization:*").WithSID("full-org-access").When(policy.Equals("principal.organization_id", "resource.id")),
// Full access to member management // Full access to member management (scoped to own organization)
policy.Allow("iam:membership:*").WithSID("full-membership-access"), policy.Allow("iam:membership:*").WithSID("full-membership-access").
// Can manage invitations When(policy.Equals("principal.organization_id", "resource.organization_id")),
// Can manage invitations (scoped to own organization)
policy.Allow( policy.Allow(
ActionIAMInvitationCreate, ActionIAMInvitationCreate,
ActionIAMInvitationGet, ActionIAMInvitationGet,
ActionIAMInvitationDelete, ActionIAMInvitationDelete,
).WithSID("manage-invitations"), ).WithSID("manage-invitations").
// Full access to SAML configuration management When(policy.Equals("principal.organization_id", "resource.organization_id")),
policy.Allow("iam:saml-configuration:*").WithSID("full-saml-access"), // Full access to SAML configuration management (scoped to own organization)
policy.Allow("iam:saml-configuration:*").WithSID("full-saml-access").
When(policy.Equals("principal.organization_id", "resource.organization_id")),
).WithDescription("Full IAM access for organization owners") ).WithDescription("Full IAM access for organization owners")
// IAMAdminPolicy defines permissions for organization admins. // IAMAdminPolicy defines permissions for organization admins.
@@ -123,19 +126,22 @@ var IAMAdminPolicy = policy.NewPolicy(
ActionIAMOrganizationListMembers, ActionIAMOrganizationListMembers,
ActionIAMOrganizationListInvitations, ActionIAMOrganizationListInvitations,
ActionIAMOrganizationInviteMember, ActionIAMOrganizationInviteMember,
).WithSID("org-admin-access"), ).WithSID("org-admin-access").When(policy.Equals("principal.organization_id", "resource.organization_id")),
// Can manage memberships (but not remove owner) // Can manage memberships (scoped to own organization)
policy.Allow( policy.Allow(
ActionIAMMembershipGet, ActionIAMMembershipGet,
ActionIAMMembershipUpdate, ActionIAMMembershipUpdate,
).WithSID("membership-admin-access"), ).WithSID("membership-admin-access").
// Can manage invitations When(policy.Equals("principal.organization_id", "resource.organization_id")),
// Can manage invitations (scoped to own organization)
policy.Allow( policy.Allow(
ActionIAMInvitationGet, ActionIAMInvitationGet,
ActionIAMInvitationDelete, ActionIAMInvitationDelete,
).WithSID("invitation-admin-access"), ).WithSID("invitation-admin-access").
// Can view and update SAML configurations When(policy.Equals("principal.organization_id", "resource.organization_id")),
policy.Allow(ActionIAMSAMLConfigurationGet).WithSID("saml-configuration-admin-access"), // Can view SAML configurations (scoped to own organization)
policy.Allow(ActionIAMSAMLConfigurationGet).WithSID("saml-configuration-admin-access").
When(policy.Equals("principal.organization_id", "resource.organization_id")),
// Cannot delete organization // Cannot delete organization
policy.Deny(ActionIAMOrganizationDelete).WithSID("deny-org-delete"), policy.Deny(ActionIAMOrganizationDelete).WithSID("deny-org-delete"),
// Cannot remove members (only owner can) // Cannot remove members (only owner can)
@@ -156,7 +162,8 @@ var IAMViewerPolicy = policy.NewPolicy(
policy.Allow( policy.Allow(
ActionIAMOrganizationGet, ActionIAMOrganizationGet,
ActionIAMOrganizationListMembers, ActionIAMOrganizationListMembers,
).WithSID("org-viewer-access"), ).WithSID("org-viewer-access").When(policy.Equals("principal.organization_id", "resource.id")),
// Can view memberships // Can view memberships (scoped to own organization)
policy.Allow(ActionIAMMembershipGet).WithSID("membership-viewer-access"), policy.Allow(ActionIAMMembershipGet).WithSID("membership-viewer-access").
When(policy.Equals("principal.organization_id", "resource.organization_id")),
).WithDescription("Read-only IAM access for organization viewers") ).WithDescription("Read-only IAM access for organization viewers")

View File

@@ -131,4 +131,3 @@ func ForEntityType(entityType uint16) ResourcePattern {
EntityType: &entityType, EntityType: &entityType,
} }
} }

View File

@@ -15,6 +15,8 @@
package policy package policy
import ( import (
"strings"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
) )
@@ -139,7 +141,22 @@ func (c Condition) Evaluate(ctx ConditionContext) bool {
case ConditionIn: case ConditionIn:
for _, v := range c.Values { for _, v := range c.Values {
resolved, ok := resolveValue(v, ctx) resolved, ok := resolveValue(v, ctx)
if ok && value == resolved { if !ok {
continue
}
// Support a comma-separated "set" value, e.g.
// principal.organization_ids = "org_1,org_2"
if strings.Contains(resolved, ",") {
for _, item := range strings.Split(resolved, ",") {
if value == strings.TrimSpace(item) {
return true
}
}
continue
}
if value == resolved {
return true return true
} }
} }
@@ -148,7 +165,20 @@ func (c Condition) Evaluate(ctx ConditionContext) bool {
case ConditionNotIn: case ConditionNotIn:
for _, v := range c.Values { for _, v := range c.Values {
resolved, ok := resolveValue(v, ctx) resolved, ok := resolveValue(v, ctx)
if ok && value == resolved { if !ok {
continue
}
if strings.Contains(resolved, ",") {
for _, item := range strings.Split(resolved, ",") {
if value == strings.TrimSpace(item) {
return false
}
}
continue
}
if value == resolved {
return false return false
} }
} }

View File

@@ -88,15 +88,28 @@ func TestCondition_Evaluate_Equals(t *testing.T) {
want: false, want: false,
}, },
{ {
name: "equals - match resource.user_id reference", name: "equals - match resource.identity_id reference",
condition: Condition{ condition: Condition{
Operator: ConditionEquals, Operator: ConditionEquals,
Key: "principal.id", Key: "principal.id",
Values: []string{"resource.user_id"}, Values: []string{"resource.identity_id"},
}, },
ctx: ConditionContext{ ctx: ConditionContext{
Principal: map[string]string{"id": "user_123"}, Principal: map[string]string{"id": "user_123"},
Resource: map[string]string{"user_id": "user_123"}, Resource: map[string]string{"identity_id": "user_123"},
},
want: true,
},
{
name: "equals - match principal.email to resource.email reference",
condition: Condition{
Operator: ConditionEquals,
Key: "principal.email",
Values: []string{"resource.email"},
},
ctx: ConditionContext{
Principal: map[string]string{"email": "user@example.com"},
Resource: map[string]string{"email": "user@example.com"},
}, },
want: true, want: true,
}, },

View File

@@ -16,21 +16,21 @@ package iam
import "go.probo.inc/probo/pkg/iam/policy" import "go.probo.inc/probo/pkg/iam/policy"
// PolicySet holds role-based and self-management policies. // PolicySet holds organization-scoped (role) policies and identity-scoped policies.
// Services create their own PolicySet and combine them when creating the Authorizer. // Services create their own PolicySet and combine them when creating the Authorizer.
type PolicySet struct { type PolicySet struct {
// RolePolicies maps role names to policies. // RolePolicies maps role names to policies.
RolePolicies map[string][]*policy.Policy RolePolicies map[string][]*policy.Policy
// SelfManagePolicies are applied to all authenticated users. // IdentityScopedPolicies are applied to all authenticated users, independent of organization membership.
SelfManagePolicies []*policy.Policy IdentityScopedPolicies []*policy.Policy
} }
// NewPolicySet creates an empty PolicySet. // NewPolicySet creates an empty PolicySet.
func NewPolicySet() *PolicySet { func NewPolicySet() *PolicySet {
return &PolicySet{ return &PolicySet{
RolePolicies: make(map[string][]*policy.Policy), RolePolicies: make(map[string][]*policy.Policy),
SelfManagePolicies: make([]*policy.Policy, 0), IdentityScopedPolicies: make([]*policy.Policy, 0),
} }
} }
@@ -40,9 +40,9 @@ func (ps *PolicySet) AddRolePolicy(role string, policies ...*policy.Policy) *Pol
return ps return ps
} }
// AddSelfManagePolicy adds policies applied to all authenticated users. // AddIdentityScopedPolicy adds policies applied to all authenticated users (identity-scoped).
func (ps *PolicySet) AddSelfManagePolicy(policies ...*policy.Policy) *PolicySet { func (ps *PolicySet) AddIdentityScopedPolicy(policies ...*policy.Policy) *PolicySet {
ps.SelfManagePolicies = append(ps.SelfManagePolicies, policies...) ps.IdentityScopedPolicies = append(ps.IdentityScopedPolicies, policies...)
return ps return ps
} }
@@ -51,7 +51,7 @@ func (ps *PolicySet) Merge(other *PolicySet) *PolicySet {
for role, policies := range other.RolePolicies { for role, policies := range other.RolePolicies {
ps.RolePolicies[role] = append(ps.RolePolicies[role], policies...) ps.RolePolicies[role] = append(ps.RolePolicies[role], policies...)
} }
ps.SelfManagePolicies = append(ps.SelfManagePolicies, other.SelfManagePolicies...) ps.IdentityScopedPolicies = append(ps.IdentityScopedPolicies, other.IdentityScopedPolicies...)
return ps return ps
} }
@@ -62,7 +62,7 @@ func IAMPolicySet() *PolicySet {
AddRolePolicy("VIEWER", IAMViewerPolicy). AddRolePolicy("VIEWER", IAMViewerPolicy).
AddRolePolicy("EMPLOYEE", IAMViewerPolicy). AddRolePolicy("EMPLOYEE", IAMViewerPolicy).
AddRolePolicy("AUDITOR", IAMViewerPolicy). AddRolePolicy("AUDITOR", IAMViewerPolicy).
AddSelfManagePolicy( AddIdentityScopedPolicy(
IAMSelfManageIdentityPolicy, IAMSelfManageIdentityPolicy,
IAMSelfManageSessionPolicy, IAMSelfManageSessionPolicy,
IAMSelfManageInvitationPolicy, IAMSelfManageInvitationPolicy,

View File

@@ -28,13 +28,13 @@ func TestPolicySet_AddAndMerge(t *testing.T) {
iamPolicies := NewPolicySet(). iamPolicies := NewPolicySet().
AddRolePolicy("OWNER", policy.NewPolicy("iam-owner", "IAM Owner", policy.Allow("iam:*"))). AddRolePolicy("OWNER", policy.NewPolicy("iam-owner", "IAM Owner", policy.Allow("iam:*"))).
AddRolePolicy("ADMIN", policy.NewPolicy("iam-admin", "IAM Admin", policy.Allow("iam:read:*"))). AddRolePolicy("ADMIN", policy.NewPolicy("iam-admin", "IAM Admin", policy.Allow("iam:read:*"))).
AddSelfManagePolicy(policy.NewPolicy("iam-self", "IAM Self", policy.Allow("iam:identity:get"))) AddIdentityScopedPolicy(policy.NewPolicy("iam-self", "IAM Self", policy.Allow("iam:identity:get")))
// Create second policy set (simulating Documents service) // Create second policy set (simulating Documents service)
docsPolicies := NewPolicySet(). docsPolicies := NewPolicySet().
AddRolePolicy("OWNER", policy.NewPolicy("docs-owner", "Docs Owner", policy.Allow("docs:*"))). AddRolePolicy("OWNER", policy.NewPolicy("docs-owner", "Docs Owner", policy.Allow("docs:*"))).
AddRolePolicy("VIEWER", policy.NewPolicy("docs-viewer", "Docs Viewer", policy.Allow("docs:read:*"))). AddRolePolicy("VIEWER", policy.NewPolicy("docs-viewer", "Docs Viewer", policy.Allow("docs:read:*"))).
AddSelfManagePolicy(policy.NewPolicy("docs-self", "Docs Self", policy.Allow("docs:own:*"))) AddIdentityScopedPolicy(policy.NewPolicy("docs-self", "Docs Self", policy.Allow("docs:own:*")))
// Merge them // Merge them
combined := iamPolicies.Merge(docsPolicies) combined := iamPolicies.Merge(docsPolicies)
@@ -53,8 +53,8 @@ func TestPolicySet_AddAndMerge(t *testing.T) {
require.Len(t, viewerPolicies, 1, "should have 1 VIEWER policy") require.Len(t, viewerPolicies, 1, "should have 1 VIEWER policy")
// Test self-manage policies from both services // Test self-manage policies from both services
selfPolicies := combined.SelfManagePolicies identityPolicies := combined.IdentityScopedPolicies
require.Len(t, selfPolicies, 2, "should have 2 self-manage policies") require.Len(t, identityPolicies, 2, "should have 2 identity-scoped policies")
} }
func TestIAMPolicySet(t *testing.T) { func TestIAMPolicySet(t *testing.T) {
@@ -69,5 +69,5 @@ func TestIAMPolicySet(t *testing.T) {
} }
// Should have self-manage policies // Should have self-manage policies
assert.NotEmpty(t, policySet.SelfManagePolicies, "expected self-manage policies") assert.NotEmpty(t, policySet.IdentityScopedPolicies, "expected identity-scoped policies")
} }

View File

@@ -247,3 +247,28 @@ func (s *Service) GetSAMLconfiguration(ctx context.Context, samlConfigurationID
return samlConfiguration, nil return samlConfiguration, nil
} }
func (s *Service) GetPersonalAPIKey(ctx context.Context, personalAPIKeyID gid.GID) (*coredata.PersonalAPIKey, error) {
personalAPIKey := &coredata.PersonalAPIKey{}
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := personalAPIKey.LoadByID(ctx, conn, personalAPIKeyID)
if err != nil {
if err == coredata.ErrResourceNotFound {
return NewPersonalAPIKeyNotFoundError(personalAPIKeyID)
}
return fmt.Errorf("cannot load personal API key: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return personalAPIKey, nil
}

View File

@@ -19,34 +19,32 @@ import (
"go.probo.inc/probo/pkg/iam/policy" "go.probo.inc/probo/pkg/iam/policy"
) )
var organizationCondition = policy.Equals("principal.organization_id", "resource.organization_id")
// OwnerPolicy defines permissions for organization owners. // OwnerPolicy defines permissions for organization owners.
var OwnerPolicy = policy.NewPolicy( var OwnerPolicy = policy.NewPolicy(
"probo:owner", "probo:owner",
"Probo Owner", "Probo Owner",
// Full access to all probo resources policy.Allow("core:*").WithSID("full-core-access").When(organizationCondition),
policy.Allow("core:*").WithSID("full-core-access"),
).WithDescription("Full probo access for organization owners") ).WithDescription("Full probo access for organization owners")
// AdminPolicy defines permissions for organization admins. // AdminPolicy defines permissions for organization admins.
var AdminPolicy = policy.NewPolicy( var AdminPolicy = policy.NewPolicy(
"probo:admin", "probo:admin",
"Probo Admin", "Probo Admin",
// Full access to all probo resources (same as owner for core entities) policy.Allow("core:*").WithSID("full-core-access").When(organizationCondition),
policy.Allow("core:*").WithSID("full-c ore-access"),
).WithDescription("Probo admin access - can manage core entities") ).WithDescription("Probo admin access - can manage core entities")
// ViewerPolicy defines read-only permissions for organization viewers. // ViewerPolicy defines read-only permissions for organization viewers.
var ViewerPolicy = policy.NewPolicy( var ViewerPolicy = policy.NewPolicy(
"probo:viewer", "probo:viewer",
"Probo Viewer", "Probo Viewer",
// Organization read actions
policy.Allow( policy.Allow(
ActionOrganizationGet, ActionOrganizationGet,
ActionOrganizationGetLogoUrl, ActionOrganizationGetLogoUrl,
ActionOrganizationGetHorizontalLogoUrl, ActionOrganizationGetHorizontalLogoUrl,
).WithSID("org-read-access"), ).WithSID("org-read-access").When(organizationCondition),
// Entity read actions
policy.Allow( policy.Allow(
ActionPeopleGet, ActionPeopleList, ActionPeopleGet, ActionPeopleList,
ActionVendorGet, ActionVendorList, ActionVendorGet, ActionVendorList,
@@ -79,42 +77,33 @@ var ViewerPolicy = policy.NewPolicy(
ActionMeetingGet, ActionMeetingList, ActionMeetingGet, ActionMeetingList,
ActionFileGet, ActionFileDownloadUrl, ActionFileGet, ActionFileDownloadUrl,
ActionSlackConnectionList, ActionSlackConnectionList,
).WithSID("entity-read-access"), ).WithSID("entity-read-access").When(organizationCondition),
// TrustCenter read actions
policy.Allow( policy.Allow(
ActionTrustCenterGet, ActionTrustCenterGet,
ActionTrustCenterAccessGet, ActionTrustCenterAccessList, ActionTrustCenterAccessGet, ActionTrustCenterAccessList,
ActionTrustCenterDocumentAccessList, ActionTrustCenterDocumentAccessList,
ActionTrustCenterFileGet, ActionTrustCenterFileList, ActionTrustCenterFileGetFileUrl, ActionTrustCenterFileGet, ActionTrustCenterFileList, ActionTrustCenterFileGetFileUrl,
ActionTrustCenterReferenceList, ActionTrustCenterReferenceGetLogoUrl, ActionTrustCenterReferenceList, ActionTrustCenterReferenceGetLogoUrl,
).WithSID("trust-center-read-access"), ).WithSID("trust-center-read-access").When(organizationCondition),
// CustomDomain read actions policy.Allow(ActionCustomDomainGet).WithSID("custom-domain-read").When(organizationCondition),
policy.Allow(ActionCustomDomainGet).WithSID("custom-domain-read"), policy.Allow(ActionOrganizationContextGet).WithSID("organization-context-read").When(organizationCondition),
// OrganizationContext read actions
policy.Allow(ActionOrganizationContextGet).WithSID("organization-context-read"),
// Document signing actions
policy.Allow( policy.Allow(
ActionDocumentVersionExportPDF, ActionDocumentVersionExportSignable, ActionDocumentVersionSign, ActionDocumentVersionExportPDF, ActionDocumentVersionExportSignable, ActionDocumentVersionSign,
).WithSID("document-signing"), ).WithSID("document-signing").When(organizationCondition),
).WithDescription("Read-only probo access for organization viewers") ).WithDescription("Read-only probo access for organization viewers")
// AuditorPolicy defines permissions for auditor role. // AuditorPolicy defines permissions for auditor role.
// Auditors have read access to non-employee content plus some specific auditor features.
var AuditorPolicy = policy.NewPolicy( var AuditorPolicy = policy.NewPolicy(
"probo:auditor", "probo:auditor",
"Probo Auditor", "Probo Auditor",
// Same as viewer but without employee-specific content
policy.Allow( policy.Allow(
ActionOrganizationGet, ActionOrganizationGet,
ActionOrganizationGetLogoUrl, ActionOrganizationGetLogoUrl,
ActionOrganizationGetHorizontalLogoUrl, ActionOrganizationGetHorizontalLogoUrl,
).WithSID("org-read-access"), ).WithSID("org-read-access").When(organizationCondition),
// Entity read access (same as viewer)
policy.Allow( policy.Allow(
ActionPeopleGet, ActionPeopleList, ActionPeopleGet, ActionPeopleList,
ActionVendorGet, ActionVendorList, ActionVendorGet, ActionVendorList,
@@ -145,40 +134,34 @@ var AuditorPolicy = policy.NewPolicy(
ActionSnapshotGet, ActionSnapshotList, ActionSnapshotGet, ActionSnapshotList,
ActionMeetingGet, ActionMeetingList, ActionMeetingGet, ActionMeetingList,
ActionFileGet, ActionFileDownloadUrl, ActionFileGet, ActionFileDownloadUrl,
).WithSID("entity-read-access"), ).WithSID("entity-read-access").When(organizationCondition),
// Document signing actions
policy.Allow( policy.Allow(
ActionDocumentVersionExportPDF, ActionDocumentVersionExportSignable, ActionDocumentVersionSign, ActionDocumentVersionExportPDF, ActionDocumentVersionExportSignable, ActionDocumentVersionSign,
).WithSID("document-signing"), ).WithSID("document-signing").When(organizationCondition),
).WithDescription("Read-only probo access for auditors (excludes internal/employee content)") ).WithDescription("Read-only probo access for auditors (excludes internal/employee content)")
// EmployeePolicy defines permissions for employee role. // EmployeePolicy defines permissions for employee role.
// Employees have access to internal documents and some limited read access.
var EmployeePolicy = policy.NewPolicy( var EmployeePolicy = policy.NewPolicy(
"probo:employee", "probo:employee",
"Probo Employee", "Probo Employee",
// Basic organization access
policy.Allow( policy.Allow(
ActionOrganizationGet, ActionOrganizationGet,
ActionOrganizationGetLogoUrl, ActionOrganizationGetLogoUrl,
).WithSID("org-basic-access"), ).WithSID("org-basic-access").When(organizationCondition),
// Document signing access
policy.Allow( policy.Allow(
ActionDocumentGet, ActionDocumentList, ActionDocumentGet, ActionDocumentList,
).WithSID("document-signing-access"), ).WithSID("document-signing-access").When(organizationCondition),
// Document version signing
policy.Allow( policy.Allow(
ActionDocumentVersionGet, ActionDocumentVersionList, ActionDocumentVersionGet, ActionDocumentVersionList,
ActionDocumentVersionSign, ActionDocumentVersionSign,
ActionDocumentVersionExportSignable, ActionDocumentVersionExportSignable,
).WithSID("document-version-signing"), ).WithSID("document-version-signing").When(organizationCondition),
).WithDescription("Employee access - can sign documents and view internal content") ).WithDescription("Employee access - can sign documents and view internal content")
// ProboPolicySet returns the PolicySet for the probo service. // ProboPolicySet returns the PolicySet for the probo service.
// This is registered with the IAM Authorizer when probo.Service is created.
func ProboPolicySet() *iam.PolicySet { func ProboPolicySet() *iam.PolicySet {
return iam.NewPolicySet(). return iam.NewPolicySet().
AddRolePolicy("OWNER", OwnerPolicy). AddRolePolicy("OWNER", OwnerPolicy).

View File

@@ -86,9 +86,6 @@ type Mutation {
createPersonalAPIKey( createPersonalAPIKey(
input: CreatePersonalAPIKeyInput! input: CreatePersonalAPIKeyInput!
): CreatePersonalAPIKeyPayload @session(required: PRESENT) ): CreatePersonalAPIKeyPayload @session(required: PRESENT)
revealPersonalAPIKeyToken(
input: RevealPersonalAPIKeyTokenInput!
): RevealPersonalAPIKeyTokenPayload @session(required: PRESENT)
revokePersonalAPIKey( revokePersonalAPIKey(
input: RevokePersonalAPIKeyInput! input: RevokePersonalAPIKeyInput!
): RevokePersonalAPIKeyPayload @session(required: PRESENT) ): RevokePersonalAPIKeyPayload @session(required: PRESENT)
@@ -285,11 +282,10 @@ type Session implements Node {
type PersonalAPIKey implements Node { type PersonalAPIKey implements Node {
id: ID! id: ID!
name: String! name: String!
lastUsedAt: Datetime
expiresAt: Datetime! expiresAt: Datetime!
createdAt: Datetime! createdAt: Datetime!
scopes: [TokenScope!]!
organizations: [Organization!]! token: String @goField(forceResolver: true)
permission(action: String!): Boolean! permission(action: String!): Boolean!
@goField(forceResolver: true) @goField(forceResolver: true)
@@ -576,15 +572,10 @@ input RevokeSessionInput {
input CreatePersonalAPIKeyInput { input CreatePersonalAPIKeyInput {
name: String! name: String!
expiresAt: Datetime! expiresAt: Datetime!
organizationIds: [ID!]!
} }
input RevokePersonalAPIKeyInput { input RevokePersonalAPIKeyInput {
tokenId: ID! personalAPIKeyId: ID!
}
input RevealPersonalAPIKeyTokenInput {
tokenId: ID!
} }
input CreateOrganizationInput { input CreateOrganizationInput {
@@ -751,11 +742,7 @@ type CreatePersonalAPIKeyPayload {
} }
type RevokePersonalAPIKeyPayload { type RevokePersonalAPIKeyPayload {
success: Boolean! personalAPIKeyId: ID!
}
type RevealPersonalAPIKeyTokenPayload {
token: String!
} }
type CreateOrganizationPayload { type CreateOrganizationPayload {

View File

@@ -218,7 +218,6 @@ type ComplexityRoot struct {
InviteMember func(childComplexity int, input types.InviteMemberInput) int InviteMember func(childComplexity int, input types.InviteMemberInput) int
RemoveMember func(childComplexity int, input types.RemoveMemberInput) int RemoveMember func(childComplexity int, input types.RemoveMemberInput) int
ResetPassword func(childComplexity int, input types.ResetPasswordInput) int ResetPassword func(childComplexity int, input types.ResetPasswordInput) int
RevealPersonalAPIKeyToken func(childComplexity int, input types.RevealPersonalAPIKeyTokenInput) int
RevokeAllSessions func(childComplexity int) int RevokeAllSessions func(childComplexity int) int
RevokePersonalAPIKey func(childComplexity int, input types.RevokePersonalAPIKeyInput) int RevokePersonalAPIKey func(childComplexity int, input types.RevokePersonalAPIKeyInput) int
RevokeSession func(childComplexity int, input types.RevokeSessionInput) int RevokeSession func(childComplexity int, input types.RevokeSessionInput) int
@@ -267,14 +266,12 @@ type ComplexityRoot struct {
} }
PersonalAPIKey struct { PersonalAPIKey struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
ExpiresAt func(childComplexity int) int ExpiresAt func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
LastUsedAt func(childComplexity int) int Name func(childComplexity int) int
Name func(childComplexity int) int Permission func(childComplexity int, action string) int
Organizations func(childComplexity int) int Token func(childComplexity int) int
Permission func(childComplexity int, action string) int
Scopes func(childComplexity int) int
} }
PersonalAPIKeyConnection struct { PersonalAPIKeyConnection struct {
@@ -302,16 +299,12 @@ type ComplexityRoot struct {
Success func(childComplexity int) int Success func(childComplexity int) int
} }
RevealPersonalAPIKeyTokenPayload struct {
Token func(childComplexity int) int
}
RevokeAllSessionsPayload struct { RevokeAllSessionsPayload struct {
RevokedCount func(childComplexity int) int RevokedCount func(childComplexity int) int
} }
RevokePersonalAPIKeyPayload struct { RevokePersonalAPIKeyPayload struct {
Success func(childComplexity int) int PersonalAPIKeyID func(childComplexity int) int
} }
RevokeSessionPayload struct { RevokeSessionPayload struct {
@@ -462,7 +455,6 @@ type MutationResolver interface {
RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error)
RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error) RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error)
CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error)
RevealPersonalAPIKeyToken(ctx context.Context, input types.RevealPersonalAPIKeyTokenInput) (*types.RevealPersonalAPIKeyTokenPayload, error)
RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error)
CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error)
UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error)
@@ -488,6 +480,7 @@ type OrganizationResolver interface {
Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error)
} }
type PersonalAPIKeyResolver interface { type PersonalAPIKeyResolver interface {
Token(ctx context.Context, obj *types.PersonalAPIKey) (*string, error)
Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error)
} }
type PersonalAPIKeyConnectionResolver interface { type PersonalAPIKeyConnectionResolver interface {
@@ -1146,17 +1139,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Mutation.ResetPassword(childComplexity, args["input"].(types.ResetPasswordInput)), true return e.complexity.Mutation.ResetPassword(childComplexity, args["input"].(types.ResetPasswordInput)), true
case "Mutation.revealPersonalAPIKeyToken":
if e.complexity.Mutation.RevealPersonalAPIKeyToken == nil {
break
}
args, err := ec.field_Mutation_revealPersonalAPIKeyToken_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RevealPersonalAPIKeyToken(childComplexity, args["input"].(types.RevealPersonalAPIKeyTokenInput)), true
case "Mutation.revokeAllSessions": case "Mutation.revokeAllSessions":
if e.complexity.Mutation.RevokeAllSessions == nil { if e.complexity.Mutation.RevokeAllSessions == nil {
break break
@@ -1443,24 +1425,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.PersonalAPIKey.ID(childComplexity), true return e.complexity.PersonalAPIKey.ID(childComplexity), true
case "PersonalAPIKey.lastUsedAt":
if e.complexity.PersonalAPIKey.LastUsedAt == nil {
break
}
return e.complexity.PersonalAPIKey.LastUsedAt(childComplexity), true
case "PersonalAPIKey.name": case "PersonalAPIKey.name":
if e.complexity.PersonalAPIKey.Name == nil { if e.complexity.PersonalAPIKey.Name == nil {
break break
} }
return e.complexity.PersonalAPIKey.Name(childComplexity), true return e.complexity.PersonalAPIKey.Name(childComplexity), true
case "PersonalAPIKey.organizations":
if e.complexity.PersonalAPIKey.Organizations == nil {
break
}
return e.complexity.PersonalAPIKey.Organizations(childComplexity), true
case "PersonalAPIKey.permission": case "PersonalAPIKey.permission":
if e.complexity.PersonalAPIKey.Permission == nil { if e.complexity.PersonalAPIKey.Permission == nil {
break break
@@ -1472,12 +1442,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.PersonalAPIKey.Permission(childComplexity, args["action"].(string)), true return e.complexity.PersonalAPIKey.Permission(childComplexity, args["action"].(string)), true
case "PersonalAPIKey.scopes": case "PersonalAPIKey.token":
if e.complexity.PersonalAPIKey.Scopes == nil { if e.complexity.PersonalAPIKey.Token == nil {
break break
} }
return e.complexity.PersonalAPIKey.Scopes(childComplexity), true return e.complexity.PersonalAPIKey.Token(childComplexity), true
case "PersonalAPIKeyConnection.edges": case "PersonalAPIKeyConnection.edges":
if e.complexity.PersonalAPIKeyConnection.Edges == nil { if e.complexity.PersonalAPIKeyConnection.Edges == nil {
@@ -1554,13 +1524,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.ResetPasswordPayload.Success(childComplexity), true return e.complexity.ResetPasswordPayload.Success(childComplexity), true
case "RevealPersonalAPIKeyTokenPayload.token":
if e.complexity.RevealPersonalAPIKeyTokenPayload.Token == nil {
break
}
return e.complexity.RevealPersonalAPIKeyTokenPayload.Token(childComplexity), true
case "RevokeAllSessionsPayload.revokedCount": case "RevokeAllSessionsPayload.revokedCount":
if e.complexity.RevokeAllSessionsPayload.RevokedCount == nil { if e.complexity.RevokeAllSessionsPayload.RevokedCount == nil {
break break
@@ -1568,12 +1531,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.RevokeAllSessionsPayload.RevokedCount(childComplexity), true return e.complexity.RevokeAllSessionsPayload.RevokedCount(childComplexity), true
case "RevokePersonalAPIKeyPayload.success": case "RevokePersonalAPIKeyPayload.personalAPIKeyId":
if e.complexity.RevokePersonalAPIKeyPayload.Success == nil { if e.complexity.RevokePersonalAPIKeyPayload.PersonalAPIKeyID == nil {
break break
} }
return e.complexity.RevokePersonalAPIKeyPayload.Success(childComplexity), true return e.complexity.RevokePersonalAPIKeyPayload.PersonalAPIKeyID(childComplexity), true
case "RevokeSessionPayload.success": case "RevokeSessionPayload.success":
if e.complexity.RevokeSessionPayload.Success == nil { if e.complexity.RevokeSessionPayload.Success == nil {
@@ -1934,7 +1897,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputMembershipOrder, ec.unmarshalInputMembershipOrder,
ec.unmarshalInputRemoveMemberInput, ec.unmarshalInputRemoveMemberInput,
ec.unmarshalInputResetPasswordInput, ec.unmarshalInputResetPasswordInput,
ec.unmarshalInputRevealPersonalAPIKeyTokenInput,
ec.unmarshalInputRevokePersonalAPIKeyInput, ec.unmarshalInputRevokePersonalAPIKeyInput,
ec.unmarshalInputRevokeSessionInput, ec.unmarshalInputRevokeSessionInput,
ec.unmarshalInputSAMLAttributeMappingsInput, ec.unmarshalInputSAMLAttributeMappingsInput,
@@ -2131,9 +2093,6 @@ type Mutation {
createPersonalAPIKey( createPersonalAPIKey(
input: CreatePersonalAPIKeyInput! input: CreatePersonalAPIKeyInput!
): CreatePersonalAPIKeyPayload @session(required: PRESENT) ): CreatePersonalAPIKeyPayload @session(required: PRESENT)
revealPersonalAPIKeyToken(
input: RevealPersonalAPIKeyTokenInput!
): RevealPersonalAPIKeyTokenPayload @session(required: PRESENT)
revokePersonalAPIKey( revokePersonalAPIKey(
input: RevokePersonalAPIKeyInput! input: RevokePersonalAPIKeyInput!
): RevokePersonalAPIKeyPayload @session(required: PRESENT) ): RevokePersonalAPIKeyPayload @session(required: PRESENT)
@@ -2330,11 +2289,10 @@ type Session implements Node {
type PersonalAPIKey implements Node { type PersonalAPIKey implements Node {
id: ID! id: ID!
name: String! name: String!
lastUsedAt: Datetime
expiresAt: Datetime! expiresAt: Datetime!
createdAt: Datetime! createdAt: Datetime!
scopes: [TokenScope!]!
organizations: [Organization!]! token: String @goField(forceResolver: true)
permission(action: String!): Boolean! permission(action: String!): Boolean!
@goField(forceResolver: true) @goField(forceResolver: true)
@@ -2621,15 +2579,10 @@ input RevokeSessionInput {
input CreatePersonalAPIKeyInput { input CreatePersonalAPIKeyInput {
name: String! name: String!
expiresAt: Datetime! expiresAt: Datetime!
organizationIds: [ID!]!
} }
input RevokePersonalAPIKeyInput { input RevokePersonalAPIKeyInput {
tokenId: ID! personalAPIKeyId: ID!
}
input RevealPersonalAPIKeyTokenInput {
tokenId: ID!
} }
input CreateOrganizationInput { input CreateOrganizationInput {
@@ -2796,11 +2749,7 @@ type CreatePersonalAPIKeyPayload {
} }
type RevokePersonalAPIKeyPayload { type RevokePersonalAPIKeyPayload {
success: Boolean! personalAPIKeyId: ID!
}
type RevealPersonalAPIKeyTokenPayload {
token: String!
} }
type CreateOrganizationPayload { type CreateOrganizationPayload {
@@ -3198,17 +3147,6 @@ func (ec *executionContext) field_Mutation_resetPassword_args(ctx context.Contex
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_revealPersonalAPIKeyToken_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNRevealPersonalAPIKeyTokenInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevealPersonalAPIKeyTokenInput)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_revokePersonalAPIKey_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_revokePersonalAPIKey_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -6734,69 +6672,6 @@ func (ec *executionContext) fieldContext_Mutation_createPersonalAPIKey(ctx conte
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_revealPersonalAPIKeyToken(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_revealPersonalAPIKeyToken,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RevealPersonalAPIKeyToken(ctx, fc.Args["input"].(types.RevealPersonalAPIKeyTokenInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT")
if err != nil {
var zeroVal *types.RevealPersonalAPIKeyTokenPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RevealPersonalAPIKeyTokenPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalORevealPersonalAPIKeyTokenPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevealPersonalAPIKeyTokenPayload,
true,
false,
)
}
func (ec *executionContext) fieldContext_Mutation_revealPersonalAPIKeyToken(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "token":
return ec.fieldContext_RevealPersonalAPIKeyTokenPayload_token(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type RevealPersonalAPIKeyTokenPayload", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_revealPersonalAPIKeyToken_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_revokePersonalAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_revokePersonalAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -6840,8 +6715,8 @@ func (ec *executionContext) fieldContext_Mutation_revokePersonalAPIKey(ctx conte
IsResolver: true, IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name { switch field.Name {
case "success": case "personalAPIKeyId":
return ec.fieldContext_RevokePersonalAPIKeyPayload_success(ctx, field) return ec.fieldContext_RevokePersonalAPIKeyPayload_personalAPIKeyId(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type RevokePersonalAPIKeyPayload", field.Name) return nil, fmt.Errorf("no field named %q was found under type RevokePersonalAPIKeyPayload", field.Name)
}, },
@@ -8440,35 +8315,6 @@ func (ec *executionContext) fieldContext_PersonalAPIKey_name(_ context.Context,
return fc, nil return fc, nil
} }
func (ec *executionContext) _PersonalAPIKey_lastUsedAt(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_PersonalAPIKey_lastUsedAt,
func(ctx context.Context) (any, error) {
return obj.LastUsedAt, nil
},
nil,
ec.marshalODatetime2ᚖtimeᚐTime,
true,
false,
)
}
func (ec *executionContext) fieldContext_PersonalAPIKey_lastUsedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "PersonalAPIKey",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Datetime does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _PersonalAPIKey_expiresAt(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) { func (ec *executionContext) _PersonalAPIKey_expiresAt(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -8527,91 +8373,30 @@ func (ec *executionContext) fieldContext_PersonalAPIKey_createdAt(_ context.Cont
return fc, nil return fc, nil
} }
func (ec *executionContext) _PersonalAPIKey_scopes(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) { func (ec *executionContext) _PersonalAPIKey_token(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
ec.OperationContext, ec.OperationContext,
field, field,
ec.fieldContext_PersonalAPIKey_scopes, ec.fieldContext_PersonalAPIKey_token,
func(ctx context.Context) (any, error) { func(ctx context.Context) (any, error) {
return obj.Scopes, nil return ec.resolvers.PersonalAPIKey().Token(ctx, obj)
}, },
nil, nil,
ec.marshalNTokenScope2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScopeᚄ, ec.marshalOString2ᚖstring,
true,
true, true,
false,
) )
} }
func (ec *executionContext) fieldContext_PersonalAPIKey_scopes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_PersonalAPIKey_token(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{ fc = &graphql.FieldContext{
Object: "PersonalAPIKey", Object: "PersonalAPIKey",
Field: field, Field: field,
IsMethod: false, IsMethod: true,
IsResolver: false, IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type TokenScope does not have child fields") return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _PersonalAPIKey_organizations(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_PersonalAPIKey_organizations,
func(ctx context.Context) (any, error) {
return obj.Organizations, nil
},
nil,
ec.marshalNOrganization2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐOrganizationᚄ,
true,
true,
)
}
func (ec *executionContext) fieldContext_PersonalAPIKey_organizations(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "PersonalAPIKey",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "email":
return ec.fieldContext_Organization_email(ctx, field)
case "description":
return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl":
return ec.fieldContext_Organization_websiteUrl(ctx, field)
case "headquarterAddress":
return ec.fieldContext_Organization_headquarterAddress(ctx, field)
case "createdAt":
return ec.fieldContext_Organization_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_Organization_updatedAt(ctx, field)
case "members":
return ec.fieldContext_Organization_members(ctx, field)
case "invitations":
return ec.fieldContext_Organization_invitations(ctx, field)
case "samlConfigurations":
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
case "viewerMembership":
return ec.fieldContext_Organization_viewerMembership(ctx, field)
case "permission":
return ec.fieldContext_Organization_permission(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
}, },
} }
return fc, nil return fc, nil
@@ -8807,16 +8592,12 @@ func (ec *executionContext) fieldContext_PersonalAPIKeyEdge_node(_ context.Conte
return ec.fieldContext_PersonalAPIKey_id(ctx, field) return ec.fieldContext_PersonalAPIKey_id(ctx, field)
case "name": case "name":
return ec.fieldContext_PersonalAPIKey_name(ctx, field) return ec.fieldContext_PersonalAPIKey_name(ctx, field)
case "lastUsedAt":
return ec.fieldContext_PersonalAPIKey_lastUsedAt(ctx, field)
case "expiresAt": case "expiresAt":
return ec.fieldContext_PersonalAPIKey_expiresAt(ctx, field) return ec.fieldContext_PersonalAPIKey_expiresAt(ctx, field)
case "createdAt": case "createdAt":
return ec.fieldContext_PersonalAPIKey_createdAt(ctx, field) return ec.fieldContext_PersonalAPIKey_createdAt(ctx, field)
case "scopes": case "token":
return ec.fieldContext_PersonalAPIKey_scopes(ctx, field) return ec.fieldContext_PersonalAPIKey_token(ctx, field)
case "organizations":
return ec.fieldContext_PersonalAPIKey_organizations(ctx, field)
case "permission": case "permission":
return ec.fieldContext_PersonalAPIKey_permission(ctx, field) return ec.fieldContext_PersonalAPIKey_permission(ctx, field)
} }
@@ -9218,35 +8999,6 @@ func (ec *executionContext) fieldContext_ResetPasswordPayload_success(_ context.
return fc, nil return fc, nil
} }
func (ec *executionContext) _RevealPersonalAPIKeyTokenPayload_token(ctx context.Context, field graphql.CollectedField, obj *types.RevealPersonalAPIKeyTokenPayload) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_RevealPersonalAPIKeyTokenPayload_token,
func(ctx context.Context) (any, error) {
return obj.Token, nil
},
nil,
ec.marshalNString2string,
true,
true,
)
}
func (ec *executionContext) fieldContext_RevealPersonalAPIKeyTokenPayload_token(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "RevealPersonalAPIKeyTokenPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _RevokeAllSessionsPayload_revokedCount(ctx context.Context, field graphql.CollectedField, obj *types.RevokeAllSessionsPayload) (ret graphql.Marshaler) { func (ec *executionContext) _RevokeAllSessionsPayload_revokedCount(ctx context.Context, field graphql.CollectedField, obj *types.RevokeAllSessionsPayload) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -9276,30 +9028,30 @@ func (ec *executionContext) fieldContext_RevokeAllSessionsPayload_revokedCount(_
return fc, nil return fc, nil
} }
func (ec *executionContext) _RevokePersonalAPIKeyPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.RevokePersonalAPIKeyPayload) (ret graphql.Marshaler) { func (ec *executionContext) _RevokePersonalAPIKeyPayload_personalAPIKeyId(ctx context.Context, field graphql.CollectedField, obj *types.RevokePersonalAPIKeyPayload) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
ec.OperationContext, ec.OperationContext,
field, field,
ec.fieldContext_RevokePersonalAPIKeyPayload_success, ec.fieldContext_RevokePersonalAPIKeyPayload_personalAPIKeyId,
func(ctx context.Context) (any, error) { func(ctx context.Context) (any, error) {
return obj.Success, nil return obj.PersonalAPIKeyID, nil
}, },
nil, nil,
ec.marshalNBoolean2bool, ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID,
true, true,
true, true,
) )
} }
func (ec *executionContext) fieldContext_RevokePersonalAPIKeyPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_RevokePersonalAPIKeyPayload_personalAPIKeyId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{ fc = &graphql.FieldContext{
Object: "RevokePersonalAPIKeyPayload", Object: "RevokePersonalAPIKeyPayload",
Field: field, Field: field,
IsMethod: false, IsMethod: false,
IsResolver: false, IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields") return nil, errors.New("field of type ID does not have child fields")
}, },
} }
return fc, nil return fc, nil
@@ -12757,7 +12509,7 @@ func (ec *executionContext) unmarshalInputCreatePersonalAPIKeyInput(ctx context.
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"name", "expiresAt", "organizationIds"} fieldsInOrder := [...]string{"name", "expiresAt"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@@ -12778,13 +12530,6 @@ func (ec *executionContext) unmarshalInputCreatePersonalAPIKeyInput(ctx context.
return it, err return it, err
} }
it.ExpiresAt = data it.ExpiresAt = data
case "organizationIds":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationIds"))
data, err := ec.unmarshalNID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v)
if err != nil {
return it, err
}
it.OrganizationIds = data
} }
} }
@@ -13200,33 +12945,6 @@ func (ec *executionContext) unmarshalInputResetPasswordInput(ctx context.Context
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputRevealPersonalAPIKeyTokenInput(ctx context.Context, obj any) (types.RevealPersonalAPIKeyTokenInput, error) {
var it types.RevealPersonalAPIKeyTokenInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"tokenId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "tokenId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tokenId"))
data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.TokenID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputRevokePersonalAPIKeyInput(ctx context.Context, obj any) (types.RevokePersonalAPIKeyInput, error) { func (ec *executionContext) unmarshalInputRevokePersonalAPIKeyInput(ctx context.Context, obj any) (types.RevokePersonalAPIKeyInput, error) {
var it types.RevokePersonalAPIKeyInput var it types.RevokePersonalAPIKeyInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -13234,20 +12952,20 @@ func (ec *executionContext) unmarshalInputRevokePersonalAPIKeyInput(ctx context.
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"tokenId"} fieldsInOrder := [...]string{"personalAPIKeyId"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
continue continue
} }
switch k { switch k {
case "tokenId": case "personalAPIKeyId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tokenId")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("personalAPIKeyId"))
data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.TokenID = data it.PersonalAPIKeyID = data
} }
} }
@@ -15397,10 +15115,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_createPersonalAPIKey(ctx, field) return ec._Mutation_createPersonalAPIKey(ctx, field)
}) })
case "revealPersonalAPIKeyToken":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_revealPersonalAPIKeyToken(ctx, field)
})
case "revokePersonalAPIKey": case "revokePersonalAPIKey":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_revokePersonalAPIKey(ctx, field) return ec._Mutation_revokePersonalAPIKey(ctx, field)
@@ -15927,8 +15641,6 @@ func (ec *executionContext) _PersonalAPIKey(ctx context.Context, sel ast.Selecti
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) atomic.AddUint32(&out.Invalids, 1)
} }
case "lastUsedAt":
out.Values[i] = ec._PersonalAPIKey_lastUsedAt(ctx, field, obj)
case "expiresAt": case "expiresAt":
out.Values[i] = ec._PersonalAPIKey_expiresAt(ctx, field, obj) out.Values[i] = ec._PersonalAPIKey_expiresAt(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@@ -15939,16 +15651,39 @@ func (ec *executionContext) _PersonalAPIKey(ctx context.Context, sel ast.Selecti
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) atomic.AddUint32(&out.Invalids, 1)
} }
case "scopes": case "token":
out.Values[i] = ec._PersonalAPIKey_scopes(ctx, field, obj) field := field
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._PersonalAPIKey_token(ctx, field, obj)
return res
} }
case "organizations":
out.Values[i] = ec._PersonalAPIKey_organizations(ctx, field, obj) if field.Deferrable != nil {
if out.Values[i] == graphql.Null { dfs, ok := deferred[field.Deferrable.Label]
atomic.AddUint32(&out.Invalids, 1) di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
} }
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "permission": case "permission":
field := field field := field
@@ -16317,45 +16052,6 @@ func (ec *executionContext) _ResetPasswordPayload(ctx context.Context, sel ast.S
return out return out
} }
var revealPersonalAPIKeyTokenPayloadImplementors = []string{"RevealPersonalAPIKeyTokenPayload"}
func (ec *executionContext) _RevealPersonalAPIKeyTokenPayload(ctx context.Context, sel ast.SelectionSet, obj *types.RevealPersonalAPIKeyTokenPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, revealPersonalAPIKeyTokenPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("RevealPersonalAPIKeyTokenPayload")
case "token":
out.Values[i] = ec._RevealPersonalAPIKeyTokenPayload_token(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var revokeAllSessionsPayloadImplementors = []string{"RevokeAllSessionsPayload"} var revokeAllSessionsPayloadImplementors = []string{"RevokeAllSessionsPayload"}
func (ec *executionContext) _RevokeAllSessionsPayload(ctx context.Context, sel ast.SelectionSet, obj *types.RevokeAllSessionsPayload) graphql.Marshaler { func (ec *executionContext) _RevokeAllSessionsPayload(ctx context.Context, sel ast.SelectionSet, obj *types.RevokeAllSessionsPayload) graphql.Marshaler {
@@ -16406,8 +16102,8 @@ func (ec *executionContext) _RevokePersonalAPIKeyPayload(ctx context.Context, se
switch field.Name { switch field.Name {
case "__typename": case "__typename":
out.Values[i] = graphql.MarshalString("RevokePersonalAPIKeyPayload") out.Values[i] = graphql.MarshalString("RevokePersonalAPIKeyPayload")
case "success": case "personalAPIKeyId":
out.Values[i] = ec._RevokePersonalAPIKeyPayload_success(ctx, field, obj) out.Values[i] = ec._RevokePersonalAPIKeyPayload_personalAPIKeyId(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
@@ -18012,36 +17708,6 @@ func (ec *executionContext) marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGI
return res return res
} }
func (ec *executionContext) unmarshalNID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx context.Context, v any) ([]gid.GID, error) {
var vSlice []any
vSlice = graphql.CoerceList(v)
var err error
res := make([]gid.GID, len(vSlice))
for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
res[i], err = ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
}
func (ec *executionContext) marshalNID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx context.Context, sel ast.SelectionSet, v []gid.GID) graphql.Marshaler {
ret := make(graphql.Array, len(v))
for i := range v {
ret[i] = ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, sel, v[i])
}
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v any) (int, error) { func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v any) (int, error) {
res, err := graphql.UnmarshalInt(v) res, err := graphql.UnmarshalInt(v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
@@ -18345,50 +18011,6 @@ var (
} }
) )
func (ec *executionContext) marshalNOrganization2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐOrganizationᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.Organization) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNOrganization2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐOrganization(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalNOrganization2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐOrganization(ctx context.Context, sel ast.SelectionSet, v *types.Organization) graphql.Marshaler { func (ec *executionContext) marshalNOrganization2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐOrganization(ctx context.Context, sel ast.SelectionSet, v *types.Organization) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -18487,11 +18109,6 @@ func (ec *executionContext) unmarshalNResetPasswordInput2goᚗproboᚗincᚋprob
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) unmarshalNRevealPersonalAPIKeyTokenInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevealPersonalAPIKeyTokenInput(ctx context.Context, v any) (types.RevealPersonalAPIKeyTokenInput, error) {
res, err := ec.unmarshalInputRevealPersonalAPIKeyTokenInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNRevokePersonalAPIKeyInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevokePersonalAPIKeyInput(ctx context.Context, v any) (types.RevokePersonalAPIKeyInput, error) { func (ec *executionContext) unmarshalNRevokePersonalAPIKeyInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevokePersonalAPIKeyInput(ctx context.Context, v any) (types.RevokePersonalAPIKeyInput, error) {
res, err := ec.unmarshalInputRevokePersonalAPIKeyInput(ctx, v) res, err := ec.unmarshalInputRevokePersonalAPIKeyInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
@@ -18769,75 +18386,6 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S
return res return res
} }
func (ec *executionContext) unmarshalNTokenScope2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScope(ctx context.Context, v any) (types.TokenScope, error) {
var res types.TokenScope
err := res.UnmarshalGQL(v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNTokenScope2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScope(ctx context.Context, sel ast.SelectionSet, v types.TokenScope) graphql.Marshaler {
return v
}
func (ec *executionContext) unmarshalNTokenScope2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScopeᚄ(ctx context.Context, v any) ([]types.TokenScope, error) {
var vSlice []any
vSlice = graphql.CoerceList(v)
var err error
res := make([]types.TokenScope, len(vSlice))
for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
res[i], err = ec.unmarshalNTokenScope2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScope(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
}
func (ec *executionContext) marshalNTokenScope2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScopeᚄ(ctx context.Context, sel ast.SelectionSet, v []types.TokenScope) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNTokenScope2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐTokenScope(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) unmarshalNUpdateMembershipInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐUpdateMembershipInput(ctx context.Context, v any) (types.UpdateMembershipInput, error) { func (ec *executionContext) unmarshalNUpdateMembershipInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐUpdateMembershipInput(ctx context.Context, v any) (types.UpdateMembershipInput, error) {
res, err := ec.unmarshalInputUpdateMembershipInput(ctx, v) res, err := ec.unmarshalInputUpdateMembershipInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
@@ -19436,13 +18984,6 @@ func (ec *executionContext) marshalOResetPasswordPayload2ᚖgoᚗproboᚗincᚋp
return ec._ResetPasswordPayload(ctx, sel, v) return ec._ResetPasswordPayload(ctx, sel, v)
} }
func (ec *executionContext) marshalORevealPersonalAPIKeyTokenPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevealPersonalAPIKeyTokenPayload(ctx context.Context, sel ast.SelectionSet, v *types.RevealPersonalAPIKeyTokenPayload) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._RevealPersonalAPIKeyTokenPayload(ctx, sel, v)
}
func (ec *executionContext) marshalORevokeAllSessionsPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevokeAllSessionsPayload(ctx context.Context, sel ast.SelectionSet, v *types.RevokeAllSessionsPayload) graphql.Marshaler { func (ec *executionContext) marshalORevokeAllSessionsPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRevokeAllSessionsPayload(ctx context.Context, sel ast.SelectionSet, v *types.RevokeAllSessionsPayload) graphql.Marshaler {
if v == nil { if v == nil {
return graphql.Null return graphql.Null

View File

@@ -78,9 +78,8 @@ type CreateOrganizationPayload struct {
} }
type CreatePersonalAPIKeyInput struct { type CreatePersonalAPIKeyInput struct {
Name string `json:"name"` Name string `json:"name"`
ExpiresAt time.Time `json:"expiresAt"` ExpiresAt time.Time `json:"expiresAt"`
OrganizationIds []gid.GID `json:"organizationIds"`
} }
type CreatePersonalAPIKeyPayload struct { type CreatePersonalAPIKeyPayload struct {
@@ -273,14 +272,12 @@ type PasswordRequired struct {
func (PasswordRequired) IsAssumeOrganizationSessionResult() {} func (PasswordRequired) IsAssumeOrganizationSessionResult() {}
type PersonalAPIKey struct { type PersonalAPIKey struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
LastUsedAt *time.Time `json:"lastUsedAt,omitempty"` ExpiresAt time.Time `json:"expiresAt"`
ExpiresAt time.Time `json:"expiresAt"` CreatedAt time.Time `json:"createdAt"`
CreatedAt time.Time `json:"createdAt"` Token *string `json:"token,omitempty"`
Scopes []TokenScope `json:"scopes"` Permission bool `json:"permission"`
Organizations []*Organization `json:"organizations"`
Permission bool `json:"permission"`
} }
func (PersonalAPIKey) IsNode() {} func (PersonalAPIKey) IsNode() {}
@@ -312,24 +309,16 @@ type ResetPasswordPayload struct {
Success bool `json:"success"` Success bool `json:"success"`
} }
type RevealPersonalAPIKeyTokenInput struct {
TokenID gid.GID `json:"tokenId"`
}
type RevealPersonalAPIKeyTokenPayload struct {
Token string `json:"token"`
}
type RevokeAllSessionsPayload struct { type RevokeAllSessionsPayload struct {
RevokedCount int `json:"revokedCount"` RevokedCount int `json:"revokedCount"`
} }
type RevokePersonalAPIKeyInput struct { type RevokePersonalAPIKeyInput struct {
TokenID gid.GID `json:"tokenId"` PersonalAPIKeyID gid.GID `json:"personalAPIKeyId"`
} }
type RevokePersonalAPIKeyPayload struct { type RevokePersonalAPIKeyPayload struct {
Success bool `json:"success"` PersonalAPIKeyID gid.GID `json:"personalAPIKeyId"`
} }
type RevokeSessionInput struct { type RevokeSessionInput struct {

View File

@@ -677,30 +677,17 @@ func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types
}, nil }, nil
} }
// RevealPersonalAPIKeyToken is the resolver for the revealPersonalAPIKeyToken field.
func (r *mutationResolver) RevealPersonalAPIKeyToken(ctx context.Context, input types.RevealPersonalAPIKeyTokenInput) (*types.RevealPersonalAPIKeyTokenPayload, error) {
identity := IdentityFromContext(ctx)
token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, input.TokenID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return &types.RevealPersonalAPIKeyTokenPayload{Token: token}, nil
}
// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field. // RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field.
func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) { func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) {
identity := IdentityFromContext(ctx) identity := IdentityFromContext(ctx)
err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.TokenID) err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.PersonalAPIKeyID)
if err != nil { if err != nil {
r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err))
return nil, gqlutils.InternalServerError(ctx) return nil, gqlutils.InternalServerError(ctx)
} }
return &types.RevokePersonalAPIKeyPayload{Success: true}, nil return &types.RevokePersonalAPIKeyPayload{PersonalAPIKeyID: input.PersonalAPIKeyID}, nil
} }
// CreateOrganization is the resolver for the createOrganization field. // CreateOrganization is the resolver for the createOrganization field.
@@ -1012,6 +999,15 @@ func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types
// Members is the resolver for the members field. // Members is the resolver for the members field.
func (r *organizationResolver) Members(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error) { func (r *organizationResolver) Members(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error) {
if err := r.iam.Authorizer.Authorize(ctx, iam.AuthorizeParams{
Principal: IdentityFromContext(ctx).ID,
Resource: obj.ID,
Action: iam.ActionIAMOrganizationListMembers,
ResourceAttributes: map[string]string{},
}); err != nil {
return nil, gqlutils.Forbidden(err)
}
if gqlutils.OnlyTotalCountSelected(ctx) { if gqlutils.OnlyTotalCountSelected(ctx) {
return &types.MembershipConnection{ return &types.MembershipConnection{
Resolver: r, Resolver: r,
@@ -1112,6 +1108,19 @@ func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organi
return r.Resolver.Permission(ctx, obj, action) return r.Resolver.Permission(ctx, obj, action)
} }
// Token is the resolver for the token field.
func (r *personalAPIKeyResolver) Token(ctx context.Context, obj *types.PersonalAPIKey) (*string, error) {
identity := IdentityFromContext(ctx)
token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err))
return nil, gqlutils.InternalServerError(ctx)
}
return &token, nil
}
// Permission is the resolver for the permission field. // Permission is the resolver for the permission field.
func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) { func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action) return r.Resolver.Permission(ctx, obj, action)
@@ -1202,6 +1211,15 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
return types.NewSAMLConfiguration(samlConfiguration), nil return types.NewSAMLConfiguration(samlConfiguration), nil
} }
case coredata.PersonalAPIKeyEntityType:
action = iam.ActionIAMPersonalAPIKeyGet
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
personalAPIKey, err := r.iam.GetPersonalAPIKey(ctx, id)
if err != nil {
return nil, err
}
return types.NewPersonalAPIKey(personalAPIKey), nil
}
default: default:
return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType()) return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType())
} }