@sanity/cli 8.20.0 → 8.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/tokens/formatTokenAccess.js +18 -0
- package/dist/actions/tokens/formatTokenAccess.js.map +1 -0
- package/dist/actions/tokens/grants.js +12 -0
- package/dist/actions/tokens/grants.js.map +1 -1
- package/dist/actions/tokens/types.js.map +1 -1
- package/dist/commands/tokens/delete.js +38 -30
- package/dist/commands/tokens/delete.js.map +1 -1
- package/dist/commands/tokens/list.js +25 -19
- package/dist/commands/tokens/list.js.map +1 -1
- package/dist/services/tokens.js +32 -22
- package/dist/services/tokens.js.map +1 -1
- package/dist/util/resolveTokenScope.js +24 -0
- package/dist/util/resolveTokenScope.js.map +1 -0
- package/oclif.manifest.json +1947 -1913
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getProjectMembership } from '../../services/tokens.js';
|
|
2
|
+
import { ORG_ROLE_LABELS } from './grants.js';
|
|
3
|
+
export function formatTokenAccess(token, scope) {
|
|
4
|
+
if (scope.resourceType === 'project') {
|
|
5
|
+
return getProjectMembership(token, scope.resourceId)?.roleNames.join(', ') || 'No roles';
|
|
6
|
+
}
|
|
7
|
+
const access = token.memberships.flatMap(({ resourceId, resourceType, roleNames })=>{
|
|
8
|
+
if (resourceType !== 'organization') {
|
|
9
|
+
return [
|
|
10
|
+
`${resourceType}:${resourceId}: ${roleNames.join(', ') || 'No roles'}`
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
return roleNames.map((name)=>ORG_ROLE_LABELS.get(name) ?? `${resourceType}:${resourceId}: ${name}`);
|
|
14
|
+
});
|
|
15
|
+
return access.join('; ') || 'No roles';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=formatTokenAccess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/tokens/formatTokenAccess.ts"],"sourcesContent":["import {getProjectMembership} from '../../services/tokens.js'\nimport {ORG_ROLE_LABELS} from './grants.js'\nimport {type Robot, type TokenScope} from './types.js'\n\nexport function formatTokenAccess(token: Robot, scope: TokenScope): string {\n if (scope.resourceType === 'project') {\n return getProjectMembership(token, scope.resourceId)?.roleNames.join(', ') || 'No roles'\n }\n\n const access = token.memberships.flatMap(({resourceId, resourceType, roleNames}) => {\n if (resourceType !== 'organization') {\n return [`${resourceType}:${resourceId}: ${roleNames.join(', ') || 'No roles'}`]\n }\n\n return roleNames.map(\n (name) => ORG_ROLE_LABELS.get(name) ?? `${resourceType}:${resourceId}: ${name}`,\n )\n })\n\n return access.join('; ') || 'No roles'\n}\n"],"names":["getProjectMembership","ORG_ROLE_LABELS","formatTokenAccess","token","scope","resourceType","resourceId","roleNames","join","access","memberships","flatMap","map","name","get"],"mappings":"AAAA,SAAQA,oBAAoB,QAAO,2BAA0B;AAC7D,SAAQC,eAAe,QAAO,cAAa;AAG3C,OAAO,SAASC,kBAAkBC,KAAY,EAAEC,KAAiB;IAC/D,IAAIA,MAAMC,YAAY,KAAK,WAAW;QACpC,OAAOL,qBAAqBG,OAAOC,MAAME,UAAU,GAAGC,UAAUC,KAAK,SAAS;IAChF;IAEA,MAAMC,SAASN,MAAMO,WAAW,CAACC,OAAO,CAAC,CAAC,EAACL,UAAU,EAAED,YAAY,EAAEE,SAAS,EAAC;QAC7E,IAAIF,iBAAiB,gBAAgB;YACnC,OAAO;gBAAC,GAAGA,aAAa,CAAC,EAAEC,WAAW,EAAE,EAAEC,UAAUC,IAAI,CAAC,SAAS,YAAY;aAAC;QACjF;QAEA,OAAOD,UAAUK,GAAG,CAClB,CAACC,OAASZ,gBAAgBa,GAAG,CAACD,SAAS,GAAGR,aAAa,CAAC,EAAEC,WAAW,EAAE,EAAEO,MAAM;IAEnF;IAEA,OAAOJ,OAAOD,IAAI,CAAC,SAAS;AAC9B"}
|
|
@@ -67,6 +67,18 @@ export const ORG_PERMISSIONS = [
|
|
|
67
67
|
title: 'Deploy Studios (all projects)'
|
|
68
68
|
}
|
|
69
69
|
];
|
|
70
|
+
export const ORG_ROLE_LABELS = new Map(ORG_PERMISSIONS.flatMap((permission)=>{
|
|
71
|
+
if ('roleName' in permission) return [
|
|
72
|
+
[
|
|
73
|
+
permission.roleName,
|
|
74
|
+
permission.title
|
|
75
|
+
]
|
|
76
|
+
];
|
|
77
|
+
return permission.roles.map((role)=>[
|
|
78
|
+
role.roleName,
|
|
79
|
+
`${permission.title}: ${role.title}`
|
|
80
|
+
]);
|
|
81
|
+
}));
|
|
70
82
|
const findOrgPermission = (key)=>ORG_PERMISSIONS.find((permission)=>permission.key === key);
|
|
71
83
|
const isProjectGrant = (grant)=>grant.key.startsWith(PROJECT_PREFIX);
|
|
72
84
|
const orgGrants = (grants)=>grants.filter((grant)=>!isProjectGrant(grant));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/tokens/grants.ts"],"sourcesContent":["import {type Membership, type SelectedTokenRole} from './types.js'\n\ntype OrgRole = {key: string; roleName: string; title: string}\n\nexport type OrgPermission = {key: string; title: string} & ({roleName: string} | {roles: OrgRole[]})\n\nexport type Grant = {key: string; roles: string[]}\n\nexport const DEFAULT_ROLE = {\n name: 'viewer',\n title: 'Viewer',\n} satisfies SelectedTokenRole\n\nconst PROJECT_PREFIX = 'project:'\n\nconst viewer = (roleName: string): OrgRole => ({\n key: 'viewer',\n roleName,\n title: 'Viewer',\n})\nconst editor = (roleName: string): OrgRole => ({\n key: 'editor',\n roleName,\n title: 'Editor',\n})\n\nexport const ORG_PERMISSIONS: OrgPermission[] = [\n {\n key: 'context',\n roles: [\n viewer('knowledge-base-viewer-robot'),\n editor('knowledge-base-editor-robot'),\n {\n key: 'developer',\n roleName: 'knowledge-base-developer-robot',\n title: 'Developer',\n },\n ],\n title: 'Context',\n },\n {\n key: 'blueprints',\n roleName: 'blueprints-deployer-robot',\n title: 'Blueprints',\n },\n {\n key: 'media-library',\n roles: [viewer('media-library-viewer-robot'), editor('media-library-editor-robot')],\n title: 'Media Library',\n },\n {\n key: 'canvas',\n roles: [viewer('canvas-viewer-robot'), editor('canvas-editor-robot')],\n title: 'Canvas',\n },\n {\n key: 'sdk-apps',\n roleName: 'sdk-app-editor-robot',\n title: 'Manage SDK Apps',\n },\n {\n key: 'access',\n roles: [viewer('access-viewer-robot'), editor('access-manager-robot')],\n title: 'Manage Access',\n },\n {\n key: 'deploy-studios',\n roleName: 'deploy-studio-robot',\n title: 'Deploy Studios (all projects)',\n },\n]\n\nconst findOrgPermission = (key: string) =>\n ORG_PERMISSIONS.find((permission) => permission.key === key)\n\nconst isProjectGrant = (grant: Grant) => grant.key.startsWith(PROJECT_PREFIX)\n\nconst orgGrants = (grants: Grant[]) => grants.filter((grant) => !isProjectGrant(grant))\n\nexport function projectGrantKey(projectId: string): string {\n return `${PROJECT_PREFIX}${projectId}`\n}\n\nexport function parseGrant(value: string): {error: string} | {grant: Grant} {\n const [key = '', roleList, ...extra] = value.split('=')\n const roles = roleList?.split(',') ?? []\n if (extra.length > 0 || roles.includes('')) {\n return {\n error: `Invalid grant \"${value}\". Use <permission>[=<role>] or project:<projectId>[=<role>[,<role>]]`,\n }\n }\n const grant = {key, roles: roles.length > 0 ? roles : [DEFAULT_ROLE.name]}\n\n if (key.startsWith(PROJECT_PREFIX) && key.length > PROJECT_PREFIX.length) {\n return {grant}\n }\n\n const permission = findOrgPermission(key)\n if (!permission) {\n const keys = [...ORG_PERMISSIONS.map((p) => p.key), `${PROJECT_PREFIX}<projectId>`]\n return {error: `Unknown grant \"${key}\". Use one of: ${keys.join(', ')}`}\n }\n\n if ('roleName' in permission) {\n return roles.length > 0\n ? {\n error: `Grant \"${key}\" does not take a role. Pass it as --grant ${key}`,\n }\n : {grant: {key, roles: []}}\n }\n\n if (roles.length > 1) {\n return {\n error: `Grant \"${key}\" takes one role. Pass it as --grant ${key}=<role>`,\n }\n }\n\n const roleKeys = permission.roles.map((role) => role.key)\n if (roles[0] && !roleKeys.includes(roles[0])) {\n return {\n error: `Invalid role \"${roles[0]}\" for \"${key}\". Use one of: ${roleKeys.join(', ')}`,\n }\n }\n\n return {grant}\n}\n\nexport function formatGrant({key, roles}: Grant): string {\n return roles.length > 0 ? `${key}=${roles.join(',')}` : key\n}\n\nexport function findGrantConflict(grants: Grant[]): string | undefined {\n const roleByPermission = new Map<string, string | undefined>()\n for (const {key, roles} of orgGrants(grants)) {\n if (roleByPermission.has(key) && roleByPermission.get(key) !== roles[0]) {\n return `Grant \"${key}\" was given with different roles. Pass it once.`\n }\n roleByPermission.set(key, roles[0])\n }\n return undefined\n}\n\nfunction toOrganizationRoleNames(grants: Grant[]): string[] {\n const roleNames = orgGrants(grants).flatMap(({key, roles}) => {\n const permission = findOrgPermission(key)\n if (!permission) return []\n if ('roleName' in permission) return [permission.roleName]\n return permission.roles.filter((role) => roles.includes(role.key)).map((role) => role.roleName)\n })\n return [...new Set(roleNames)]\n}\n\nfunction toProjectMemberships(grants: Grant[]): Membership[] {\n const rolesByProject = new Map<string, Set<string>>()\n for (const grant of grants.filter((grant) => isProjectGrant(grant))) {\n const projectId = grant.key.slice(PROJECT_PREFIX.length)\n rolesByProject.set(\n projectId,\n new Set([...(rolesByProject.get(projectId) ?? []), ...grant.roles]),\n )\n }\n\n return [...rolesByProject].map(([resourceId, roleNames]) => ({\n resourceId,\n resourceType: 'project',\n roleNames: [...roleNames],\n }))\n}\n\nexport function getProjectTokenMembership(grants: Grant[]): Membership | undefined {\n const [membership, ...otherProjects] = toProjectMemberships(grants)\n return otherProjects.length === 0 && toOrganizationRoleNames(grants).length === 0\n ? membership\n : undefined\n}\n\nexport function toMemberships(grants: Grant[], organizationId: string): Membership[] {\n const roleNames = toOrganizationRoleNames(grants)\n const projectMemberships = toProjectMemberships(grants)\n return roleNames.length > 0\n ? [{resourceId: organizationId, resourceType: 'organization', roleNames}, ...projectMemberships]\n : projectMemberships\n}\n"],"names":["DEFAULT_ROLE","name","title","PROJECT_PREFIX","viewer","roleName","key","editor","ORG_PERMISSIONS","roles","findOrgPermission","find","permission","isProjectGrant","grant","startsWith","orgGrants","grants","filter","projectGrantKey","projectId","parseGrant","value","roleList","extra","split","length","includes","error","keys","map","p","join","roleKeys","role","formatGrant","findGrantConflict","roleByPermission","Map","has","get","set","undefined","toOrganizationRoleNames","roleNames","flatMap","Set","toProjectMemberships","rolesByProject","slice","resourceId","resourceType","getProjectTokenMembership","membership","otherProjects","toMemberships","organizationId","projectMemberships"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/tokens/grants.ts"],"sourcesContent":["import {type Membership, type SelectedTokenRole} from './types.js'\n\ntype OrgRole = {key: string; roleName: string; title: string}\n\nexport type OrgPermission = {key: string; title: string} & ({roleName: string} | {roles: OrgRole[]})\n\nexport type Grant = {key: string; roles: string[]}\n\nexport const DEFAULT_ROLE = {\n name: 'viewer',\n title: 'Viewer',\n} satisfies SelectedTokenRole\n\nconst PROJECT_PREFIX = 'project:'\n\nconst viewer = (roleName: string): OrgRole => ({\n key: 'viewer',\n roleName,\n title: 'Viewer',\n})\nconst editor = (roleName: string): OrgRole => ({\n key: 'editor',\n roleName,\n title: 'Editor',\n})\n\nexport const ORG_PERMISSIONS: OrgPermission[] = [\n {\n key: 'context',\n roles: [\n viewer('knowledge-base-viewer-robot'),\n editor('knowledge-base-editor-robot'),\n {\n key: 'developer',\n roleName: 'knowledge-base-developer-robot',\n title: 'Developer',\n },\n ],\n title: 'Context',\n },\n {\n key: 'blueprints',\n roleName: 'blueprints-deployer-robot',\n title: 'Blueprints',\n },\n {\n key: 'media-library',\n roles: [viewer('media-library-viewer-robot'), editor('media-library-editor-robot')],\n title: 'Media Library',\n },\n {\n key: 'canvas',\n roles: [viewer('canvas-viewer-robot'), editor('canvas-editor-robot')],\n title: 'Canvas',\n },\n {\n key: 'sdk-apps',\n roleName: 'sdk-app-editor-robot',\n title: 'Manage SDK Apps',\n },\n {\n key: 'access',\n roles: [viewer('access-viewer-robot'), editor('access-manager-robot')],\n title: 'Manage Access',\n },\n {\n key: 'deploy-studios',\n roleName: 'deploy-studio-robot',\n title: 'Deploy Studios (all projects)',\n },\n]\n\nexport const ORG_ROLE_LABELS = new Map<string, string>(\n ORG_PERMISSIONS.flatMap((permission) => {\n if ('roleName' in permission) return [[permission.roleName, permission.title]]\n return permission.roles.map((role) => [role.roleName, `${permission.title}: ${role.title}`])\n }),\n)\n\nconst findOrgPermission = (key: string) =>\n ORG_PERMISSIONS.find((permission) => permission.key === key)\n\nconst isProjectGrant = (grant: Grant) => grant.key.startsWith(PROJECT_PREFIX)\n\nconst orgGrants = (grants: Grant[]) => grants.filter((grant) => !isProjectGrant(grant))\n\nexport function projectGrantKey(projectId: string): string {\n return `${PROJECT_PREFIX}${projectId}`\n}\n\nexport function parseGrant(value: string): {error: string} | {grant: Grant} {\n const [key = '', roleList, ...extra] = value.split('=')\n const roles = roleList?.split(',') ?? []\n if (extra.length > 0 || roles.includes('')) {\n return {\n error: `Invalid grant \"${value}\". Use <permission>[=<role>] or project:<projectId>[=<role>[,<role>]]`,\n }\n }\n const grant = {key, roles: roles.length > 0 ? roles : [DEFAULT_ROLE.name]}\n\n if (key.startsWith(PROJECT_PREFIX) && key.length > PROJECT_PREFIX.length) {\n return {grant}\n }\n\n const permission = findOrgPermission(key)\n if (!permission) {\n const keys = [...ORG_PERMISSIONS.map((p) => p.key), `${PROJECT_PREFIX}<projectId>`]\n return {error: `Unknown grant \"${key}\". Use one of: ${keys.join(', ')}`}\n }\n\n if ('roleName' in permission) {\n return roles.length > 0\n ? {\n error: `Grant \"${key}\" does not take a role. Pass it as --grant ${key}`,\n }\n : {grant: {key, roles: []}}\n }\n\n if (roles.length > 1) {\n return {\n error: `Grant \"${key}\" takes one role. Pass it as --grant ${key}=<role>`,\n }\n }\n\n const roleKeys = permission.roles.map((role) => role.key)\n if (roles[0] && !roleKeys.includes(roles[0])) {\n return {\n error: `Invalid role \"${roles[0]}\" for \"${key}\". Use one of: ${roleKeys.join(', ')}`,\n }\n }\n\n return {grant}\n}\n\nexport function formatGrant({key, roles}: Grant): string {\n return roles.length > 0 ? `${key}=${roles.join(',')}` : key\n}\n\nexport function findGrantConflict(grants: Grant[]): string | undefined {\n const roleByPermission = new Map<string, string | undefined>()\n for (const {key, roles} of orgGrants(grants)) {\n if (roleByPermission.has(key) && roleByPermission.get(key) !== roles[0]) {\n return `Grant \"${key}\" was given with different roles. Pass it once.`\n }\n roleByPermission.set(key, roles[0])\n }\n return undefined\n}\n\nfunction toOrganizationRoleNames(grants: Grant[]): string[] {\n const roleNames = orgGrants(grants).flatMap(({key, roles}) => {\n const permission = findOrgPermission(key)\n if (!permission) return []\n if ('roleName' in permission) return [permission.roleName]\n return permission.roles.filter((role) => roles.includes(role.key)).map((role) => role.roleName)\n })\n return [...new Set(roleNames)]\n}\n\nfunction toProjectMemberships(grants: Grant[]): Membership[] {\n const rolesByProject = new Map<string, Set<string>>()\n for (const grant of grants.filter((grant) => isProjectGrant(grant))) {\n const projectId = grant.key.slice(PROJECT_PREFIX.length)\n rolesByProject.set(\n projectId,\n new Set([...(rolesByProject.get(projectId) ?? []), ...grant.roles]),\n )\n }\n\n return [...rolesByProject].map(([resourceId, roleNames]) => ({\n resourceId,\n resourceType: 'project',\n roleNames: [...roleNames],\n }))\n}\n\nexport function getProjectTokenMembership(grants: Grant[]): Membership | undefined {\n const [membership, ...otherProjects] = toProjectMemberships(grants)\n return otherProjects.length === 0 && toOrganizationRoleNames(grants).length === 0\n ? membership\n : undefined\n}\n\nexport function toMemberships(grants: Grant[], organizationId: string): Membership[] {\n const roleNames = toOrganizationRoleNames(grants)\n const projectMemberships = toProjectMemberships(grants)\n return roleNames.length > 0\n ? [{resourceId: organizationId, resourceType: 'organization', roleNames}, ...projectMemberships]\n : projectMemberships\n}\n"],"names":["DEFAULT_ROLE","name","title","PROJECT_PREFIX","viewer","roleName","key","editor","ORG_PERMISSIONS","roles","ORG_ROLE_LABELS","Map","flatMap","permission","map","role","findOrgPermission","find","isProjectGrant","grant","startsWith","orgGrants","grants","filter","projectGrantKey","projectId","parseGrant","value","roleList","extra","split","length","includes","error","keys","p","join","roleKeys","formatGrant","findGrantConflict","roleByPermission","has","get","set","undefined","toOrganizationRoleNames","roleNames","Set","toProjectMemberships","rolesByProject","slice","resourceId","resourceType","getProjectTokenMembership","membership","otherProjects","toMemberships","organizationId","projectMemberships"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/tokens/types.ts"],"sourcesContent":["export interface Role {\n appliesToRobots: boolean\n appliesToUsers: boolean\n description: string\n isCustom: boolean\n name: string\n resourceId: string\n resourceType: string\n title: string\n}\n\nexport type SelectedTokenRole = Pick<Role, 'name' | 'title'>\n\nexport interface Membership {\n resourceId: string\n resourceType: string\n roleNames: string[]\n\n addedAt?: string\n lastSeenAt?: string | null\n resourceUserId?: string | null\n}\n\nexport interface Robot {\n createdAt: string\n id: string\n label: string\n memberships: Membership[]\n\n expiresAt?: string | null\n managedBy?: {\n resourceId: string\n resourceType: string\n }\n tokenId?: string\n}\n\nexport interface RobotWithToken extends Robot {\n token: string\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/tokens/types.ts"],"sourcesContent":["export interface Role {\n appliesToRobots: boolean\n appliesToUsers: boolean\n description: string\n isCustom: boolean\n name: string\n resourceId: string\n resourceType: string\n title: string\n}\n\nexport type SelectedTokenRole = Pick<Role, 'name' | 'title'>\n\nexport interface Membership {\n resourceId: string\n resourceType: string\n roleNames: string[]\n\n addedAt?: string\n lastSeenAt?: string | null\n resourceUserId?: string | null\n}\n\nexport interface Robot {\n createdAt: string\n id: string\n label: string\n memberships: Membership[]\n\n expiresAt?: string | null\n managedBy?: {\n resourceId: string\n resourceType: string\n }\n tokenId?: string\n}\n\nexport interface RobotWithToken extends Robot {\n token: string\n}\n\nexport interface TokenScope {\n resourceId: string\n resourceType: 'organization' | 'project'\n}\n"],"names":[],"mappings":"AAyCA,WAGC"}
|
|
@@ -3,10 +3,11 @@ import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
|
3
3
|
import { requiredWhenUnattended } from '@sanity/cli-core/flags';
|
|
4
4
|
import { confirm, select } from '@sanity/cli-core/ux';
|
|
5
5
|
import { ClientError } from '@sanity/client';
|
|
6
|
-
import {
|
|
7
|
-
import { deleteToken,
|
|
6
|
+
import { formatTokenAccess } from '../../actions/tokens/formatTokenAccess.js';
|
|
7
|
+
import { deleteToken, getTokens } from '../../services/tokens.js';
|
|
8
8
|
import { formatCliErrorMessages } from '../../util/formatCliErrorMessages.js';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveTokenScope } from '../../util/resolveTokenScope.js';
|
|
10
|
+
import { getOrganizationFlag, getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
10
11
|
const deleteTokenDebug = subdebug('tokens:delete');
|
|
11
12
|
export class DeleteTokensCommand extends SanityCommand {
|
|
12
13
|
static args = {
|
|
@@ -15,8 +16,16 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
15
16
|
required: false
|
|
16
17
|
})
|
|
17
18
|
};
|
|
18
|
-
static description = 'Delete an API token from
|
|
19
|
+
static description = 'Delete an API token from a project or organization';
|
|
19
20
|
static examples = [
|
|
21
|
+
{
|
|
22
|
+
command: '<%= config.bin %> <%= command.id %> --organization org123',
|
|
23
|
+
description: 'Interactively select and delete an organization token'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
command: '<%= config.bin %> <%= command.id %> silJ2lFmK6dONB --organization org123 --yes',
|
|
27
|
+
description: 'Delete an organization token without confirmation'
|
|
28
|
+
},
|
|
20
29
|
{
|
|
21
30
|
command: '<%= config.bin %> <%= command.id %>',
|
|
22
31
|
description: 'Interactively select and delete a token'
|
|
@@ -35,6 +44,13 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
35
44
|
}
|
|
36
45
|
];
|
|
37
46
|
static flags = {
|
|
47
|
+
...getOrganizationFlag({
|
|
48
|
+
description: 'Delete a token managed by this organization',
|
|
49
|
+
exclusive: [
|
|
50
|
+
'project-id'
|
|
51
|
+
],
|
|
52
|
+
semantics: 'specify'
|
|
53
|
+
}),
|
|
38
54
|
...getProjectIdFlag({
|
|
39
55
|
description: 'Project ID to delete token from',
|
|
40
56
|
semantics: 'override'
|
|
@@ -47,7 +63,6 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
47
63
|
static hiddenAliases = [
|
|
48
64
|
'token:delete'
|
|
49
65
|
];
|
|
50
|
-
projectId;
|
|
51
66
|
async run() {
|
|
52
67
|
const { args, flags } = await this.parse(DeleteTokensCommand);
|
|
53
68
|
const skipConfirmation = flags.yes;
|
|
@@ -64,23 +79,16 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
64
79
|
});
|
|
65
80
|
}
|
|
66
81
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
grant: 'delete',
|
|
73
|
-
permission: 'sanity.project.tokens'
|
|
74
|
-
}
|
|
75
|
-
]
|
|
76
|
-
})
|
|
82
|
+
const scope = await resolveTokenScope({
|
|
83
|
+
getProjectId: this.getProjectId.bind(this),
|
|
84
|
+
grant: 'delete',
|
|
85
|
+
organization: flags.organization
|
|
77
86
|
});
|
|
78
|
-
|
|
79
|
-
const tokenId = givenTokenId || await this.getTokenIdFromList();
|
|
87
|
+
const tokenId = givenTokenId || await this.getTokenIdFromList(scope);
|
|
80
88
|
if (!skipConfirmation) {
|
|
81
89
|
const confirmed = await confirm({
|
|
82
90
|
default: false,
|
|
83
|
-
message: `Delete API token "${tokenId}"?`
|
|
91
|
+
message: scope.resourceType === 'organization' ? `Delete API token "${tokenId}" from organization "${scope.resourceId}"? This revokes all of its organization and project access.` : `Delete API token "${tokenId}"?`
|
|
84
92
|
});
|
|
85
93
|
if (!confirmed) {
|
|
86
94
|
this.log('API token not deleted');
|
|
@@ -88,10 +96,7 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
try {
|
|
91
|
-
await deleteToken(
|
|
92
|
-
projectId: this.projectId,
|
|
93
|
-
tokenId
|
|
94
|
-
});
|
|
99
|
+
await deleteToken(scope, tokenId);
|
|
95
100
|
this.log('API token deleted');
|
|
96
101
|
} catch (error) {
|
|
97
102
|
if (error instanceof ClientError && error.response.statusCode === 404) {
|
|
@@ -106,26 +111,29 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
106
111
|
});
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
|
-
async getTokenIdFromList() {
|
|
114
|
+
async getTokenIdFromList(scope) {
|
|
110
115
|
let tokens;
|
|
111
116
|
try {
|
|
112
|
-
tokens = await getTokens(
|
|
117
|
+
tokens = await getTokens(scope);
|
|
113
118
|
} catch (error) {
|
|
114
119
|
const err = error;
|
|
115
|
-
deleteTokenDebug(`Error fetching tokens for
|
|
116
|
-
this.error(`Could not list API tokens:\n${err.message}\nCheck the
|
|
120
|
+
deleteTokenDebug(`Error fetching tokens for ${scope.resourceType} ${scope.resourceId}`, err);
|
|
121
|
+
this.error(`Could not list API tokens:\n${err.message}\nCheck the ${scope.resourceType} ID and your access permissions, then try again.`, {
|
|
117
122
|
exit: exitCodes.RUNTIME_ERROR
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
if (tokens.length === 0) {
|
|
121
|
-
this.error(
|
|
126
|
+
this.error(`No API tokens found for this ${scope.resourceType}.`, {
|
|
122
127
|
exit: exitCodes.RUNTIME_ERROR
|
|
123
128
|
});
|
|
124
129
|
}
|
|
125
|
-
const choices = tokens.map((token)=>
|
|
126
|
-
|
|
130
|
+
const choices = tokens.map((token)=>{
|
|
131
|
+
const access = formatTokenAccess(token, scope);
|
|
132
|
+
return {
|
|
133
|
+
name: `${token.label} [${token.id}] (${access})`,
|
|
127
134
|
value: token.id
|
|
128
|
-
}
|
|
135
|
+
};
|
|
136
|
+
});
|
|
129
137
|
return select({
|
|
130
138
|
choices,
|
|
131
139
|
message: 'Select token to delete:'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/tokens/delete.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {confirm, select} from '@sanity/cli-core/ux'\nimport {ClientError} from '@sanity/client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/tokens/delete.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {confirm, select} from '@sanity/cli-core/ux'\nimport {ClientError} from '@sanity/client'\n\nimport {formatTokenAccess} from '../../actions/tokens/formatTokenAccess.js'\nimport {type TokenScope} from '../../actions/tokens/types.js'\nimport {deleteToken, getTokens} from '../../services/tokens.js'\nimport {formatCliErrorMessages} from '../../util/formatCliErrorMessages.js'\nimport {resolveTokenScope} from '../../util/resolveTokenScope.js'\nimport {getOrganizationFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst deleteTokenDebug = subdebug('tokens:delete')\n\nexport class DeleteTokensCommand extends SanityCommand<typeof DeleteTokensCommand> {\n static override args = {\n tokenId: Args.string({\n description: 'Token ID to delete (will prompt if not provided)',\n required: false,\n }),\n }\n\n static override description = 'Delete an API token from a project or organization'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --organization org123',\n description: 'Interactively select and delete an organization token',\n },\n {\n command: '<%= config.bin %> <%= command.id %> silJ2lFmK6dONB --organization org123 --yes',\n description: 'Delete an organization token without confirmation',\n },\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively select and delete a token',\n },\n {\n command: '<%= config.bin %> <%= command.id %> silJ2lFmK6dONB',\n description: 'Delete a specific token by ID',\n },\n {\n command: '<%= config.bin %> <%= command.id %> silJ2lFmK6dONB --yes',\n description: 'Delete a specific token without confirmation prompt',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'Delete a token from a specific project',\n },\n ]\n\n static override flags = {\n ...getOrganizationFlag({\n description: 'Delete a token managed by this organization',\n exclusive: ['project-id'],\n semantics: 'specify',\n }),\n ...getProjectIdFlag({\n description: 'Project ID to delete token from',\n semantics: 'override',\n }),\n yes: requiredWhenUnattended(\n Flags.boolean({\n char: 'y',\n description: 'Skip confirmation prompt (unattended mode)',\n }),\n ),\n }\n\n static override hiddenAliases: string[] = ['token:delete']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(DeleteTokensCommand)\n\n const skipConfirmation = flags.yes\n const unattended = this.isUnattended()\n const {tokenId: givenTokenId} = args\n\n if (unattended) {\n const errors: string[] = []\n\n if (!givenTokenId) {\n errors.push('Token ID is required. Pass it as the `<tokenId>` argument.')\n }\n if (errors.length > 0) {\n this.error(formatCliErrorMessages(errors), {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n }\n\n const scope = await resolveTokenScope({\n getProjectId: this.getProjectId.bind(this),\n grant: 'delete',\n organization: flags.organization,\n })\n\n const tokenId = givenTokenId || (await this.getTokenIdFromList(scope))\n\n if (!skipConfirmation) {\n const confirmed = await confirm({\n default: false,\n message:\n scope.resourceType === 'organization'\n ? `Delete API token \"${tokenId}\" from organization \"${scope.resourceId}\"? This revokes all of its organization and project access.`\n : `Delete API token \"${tokenId}\"?`,\n })\n\n if (!confirmed) {\n this.log('API token not deleted')\n this.exit(exitCodes.USER_ABORT)\n }\n }\n\n try {\n await deleteToken(scope, tokenId)\n\n this.log('API token deleted')\n } catch (error) {\n if (error instanceof ClientError && error.response.statusCode === 404) {\n this.error(`Token with ID \"${tokenId}\" not found`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const err = error as Error\n deleteTokenDebug(`Error deleting token`, err)\n this.error(`Token deletion failed:\\n${err.message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n private async getTokenIdFromList(scope: TokenScope) {\n let tokens: Awaited<ReturnType<typeof getTokens>>\n try {\n tokens = await getTokens(scope)\n } catch (error) {\n const err = error as Error\n deleteTokenDebug(`Error fetching tokens for ${scope.resourceType} ${scope.resourceId}`, err)\n this.error(\n `Could not list API tokens:\\n${err.message}\\nCheck the ${scope.resourceType} ID and your access permissions, then try again.`,\n {exit: exitCodes.RUNTIME_ERROR},\n )\n }\n\n if (tokens.length === 0) {\n this.error(`No API tokens found for this ${scope.resourceType}.`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n const choices = tokens.map((token) => {\n const access = formatTokenAccess(token, scope)\n return {name: `${token.label} [${token.id}] (${access})`, value: token.id}\n })\n\n return select({\n choices,\n message: 'Select token to delete:',\n })\n }\n}\n"],"names":["Args","Flags","exitCodes","SanityCommand","subdebug","requiredWhenUnattended","confirm","select","ClientError","formatTokenAccess","deleteToken","getTokens","formatCliErrorMessages","resolveTokenScope","getOrganizationFlag","getProjectIdFlag","deleteTokenDebug","DeleteTokensCommand","args","tokenId","string","description","required","examples","command","flags","exclusive","semantics","yes","boolean","char","hiddenAliases","run","parse","skipConfirmation","unattended","isUnattended","givenTokenId","errors","push","length","error","exit","USAGE_ERROR","scope","getProjectId","bind","grant","organization","getTokenIdFromList","confirmed","default","message","resourceType","resourceId","log","USER_ABORT","response","statusCode","RUNTIME_ERROR","err","tokens","choices","map","token","access","name","label","id","value"],"mappings":"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"}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
3
|
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { formatTokenAccess } from '../../actions/tokens/formatTokenAccess.js';
|
|
5
|
+
import { getTokens } from '../../services/tokens.js';
|
|
6
|
+
import { resolveTokenScope } from '../../util/resolveTokenScope.js';
|
|
6
7
|
import { Table } from '../../util/responsiveTable.js';
|
|
7
|
-
import { getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
8
|
+
import { getOrganizationFlag, getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
8
9
|
const listTokenDebug = subdebug('tokens:list');
|
|
9
10
|
export class TokensListCommand extends SanityCommand {
|
|
10
|
-
static description = 'List API tokens for
|
|
11
|
+
static description = 'List API tokens for a project or organization';
|
|
11
12
|
static examples = [
|
|
13
|
+
{
|
|
14
|
+
command: '<%= config.bin %> <%= command.id %> --organization org123',
|
|
15
|
+
description: 'List tokens managed by an organization'
|
|
16
|
+
},
|
|
12
17
|
{
|
|
13
18
|
command: '<%= config.bin %> <%= command.id %>',
|
|
14
19
|
description: 'List tokens for the project'
|
|
@@ -23,6 +28,13 @@ export class TokensListCommand extends SanityCommand {
|
|
|
23
28
|
}
|
|
24
29
|
];
|
|
25
30
|
static flags = {
|
|
31
|
+
...getOrganizationFlag({
|
|
32
|
+
description: 'List tokens managed by this organization',
|
|
33
|
+
exclusive: [
|
|
34
|
+
'project-id'
|
|
35
|
+
],
|
|
36
|
+
semantics: 'specify'
|
|
37
|
+
}),
|
|
26
38
|
...getProjectIdFlag({
|
|
27
39
|
description: 'Project ID to list tokens for',
|
|
28
40
|
semantics: 'override'
|
|
@@ -39,23 +51,17 @@ export class TokensListCommand extends SanityCommand {
|
|
|
39
51
|
const { flags } = await this.parse(TokensListCommand);
|
|
40
52
|
const { json } = flags;
|
|
41
53
|
const outputJson = json ?? false;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
grant: 'read',
|
|
48
|
-
permission: 'sanity.project.tokens'
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
})
|
|
54
|
+
const scope = await resolveTokenScope({
|
|
55
|
+
getProjectId: this.getProjectId.bind(this),
|
|
56
|
+
grant: 'read',
|
|
57
|
+
organization: flags.organization
|
|
52
58
|
});
|
|
53
59
|
let tokens;
|
|
54
60
|
try {
|
|
55
|
-
tokens = await getTokens(
|
|
61
|
+
tokens = await getTokens(scope);
|
|
56
62
|
} catch (error) {
|
|
57
63
|
const message = getErrorMessage(error);
|
|
58
|
-
listTokenDebug(`Error fetching tokens for
|
|
64
|
+
listTokenDebug(`Error fetching tokens for ${scope.resourceType} ${scope.resourceId}`, error);
|
|
59
65
|
this.error(`Token list retrieval failed:\n${message}`, {
|
|
60
66
|
exit: exitCodes.RUNTIME_ERROR
|
|
61
67
|
});
|
|
@@ -65,7 +71,7 @@ export class TokensListCommand extends SanityCommand {
|
|
|
65
71
|
return;
|
|
66
72
|
}
|
|
67
73
|
if (tokens.length === 0) {
|
|
68
|
-
this.log(
|
|
74
|
+
this.log(`No API tokens found for this ${scope.resourceType}.`);
|
|
69
75
|
return;
|
|
70
76
|
}
|
|
71
77
|
const table = new Table({
|
|
@@ -83,7 +89,7 @@ export class TokensListCommand extends SanityCommand {
|
|
|
83
89
|
{
|
|
84
90
|
alignment: 'left',
|
|
85
91
|
name: 'roles',
|
|
86
|
-
title: 'Roles'
|
|
92
|
+
title: scope.resourceType === 'organization' ? 'Access' : 'Roles'
|
|
87
93
|
},
|
|
88
94
|
{
|
|
89
95
|
alignment: 'left',
|
|
@@ -94,7 +100,7 @@ export class TokensListCommand extends SanityCommand {
|
|
|
94
100
|
title: `Found ${tokens.length} API tokens`
|
|
95
101
|
});
|
|
96
102
|
for (const token of tokens){
|
|
97
|
-
const roles =
|
|
103
|
+
const roles = formatTokenAccess(token, scope);
|
|
98
104
|
table.addRow({
|
|
99
105
|
expires: token.expiresAt ? token.expiresAt.slice(0, 10) : 'Never',
|
|
100
106
|
id: token.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/tokens/list.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\n\nimport {type Robot} from '../../actions/tokens/types.js'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/tokens/list.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\n\nimport {formatTokenAccess} from '../../actions/tokens/formatTokenAccess.js'\nimport {type Robot} from '../../actions/tokens/types.js'\nimport {getTokens} from '../../services/tokens.js'\nimport {resolveTokenScope} from '../../util/resolveTokenScope.js'\nimport {Table} from '../../util/responsiveTable.js'\nimport {getOrganizationFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst listTokenDebug = subdebug('tokens:list')\n\nexport class TokensListCommand extends SanityCommand<typeof TokensListCommand> {\n static override description = 'List API tokens for a project or organization'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --organization org123',\n description: 'List tokens managed by an organization',\n },\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List tokens for the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json',\n description: 'List tokens in JSON format',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'List tokens for a specific project',\n },\n ]\n static override flags = {\n ...getOrganizationFlag({\n description: 'List tokens managed by this organization',\n exclusive: ['project-id'],\n semantics: 'specify',\n }),\n ...getProjectIdFlag({\n description: 'Project ID to list tokens for',\n semantics: 'override',\n }),\n json: Flags.boolean({\n default: false,\n description: 'Output tokens in JSON format',\n }),\n }\n\n static override hiddenAliases: string[] = ['token:list']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(TokensListCommand)\n const {json} = flags\n const outputJson = json ?? false\n\n const scope = await resolveTokenScope({\n getProjectId: this.getProjectId.bind(this),\n grant: 'read',\n organization: flags.organization,\n })\n\n let tokens: Robot[]\n try {\n tokens = await getTokens(scope)\n } catch (error) {\n const message = getErrorMessage(error)\n listTokenDebug(`Error fetching tokens for ${scope.resourceType} ${scope.resourceId}`, error)\n this.error(`Token list retrieval failed:\\n${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (outputJson) {\n this.log(JSON.stringify(tokens, null, 2))\n return\n }\n\n if (tokens.length === 0) {\n this.log(`No API tokens found for this ${scope.resourceType}.`)\n return\n }\n\n const table = new Table({\n columns: [\n {alignment: 'left', name: 'label', title: 'Label'},\n {alignment: 'left', name: 'id', title: 'ID'},\n {\n alignment: 'left',\n name: 'roles',\n title: scope.resourceType === 'organization' ? 'Access' : 'Roles',\n },\n {alignment: 'left', name: 'expires', title: 'Expires'},\n ],\n title: `Found ${tokens.length} API tokens`,\n })\n\n for (const token of tokens) {\n const roles = formatTokenAccess(token, scope)\n\n table.addRow({\n expires: token.expiresAt ? token.expiresAt.slice(0, 10) : 'Never',\n id: token.id,\n label: token.label,\n roles,\n })\n }\n\n this.log(table.render())\n }\n}\n"],"names":["Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","formatTokenAccess","getTokens","resolveTokenScope","Table","getOrganizationFlag","getProjectIdFlag","listTokenDebug","TokensListCommand","description","examples","command","flags","exclusive","semantics","json","boolean","default","hiddenAliases","run","parse","outputJson","scope","getProjectId","bind","grant","organization","tokens","error","message","resourceType","resourceId","exit","RUNTIME_ERROR","log","JSON","stringify","length","table","columns","alignment","name","title","token","roles","addRow","expires","expiresAt","slice","id","label","render"],"mappings":"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"}
|
package/dist/services/tokens.js
CHANGED
|
@@ -9,13 +9,16 @@ function getClient(token) {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
-
async function fetchAllPages(client, url) {
|
|
12
|
+
async function fetchAllPages(client, url, query = {}) {
|
|
13
13
|
const items = [];
|
|
14
14
|
let cursor = null;
|
|
15
15
|
do {
|
|
16
16
|
const response = await client.request({
|
|
17
|
-
query:
|
|
18
|
-
|
|
17
|
+
query: {
|
|
18
|
+
...query,
|
|
19
|
+
...cursor === null ? {} : {
|
|
20
|
+
nextCursor: cursor
|
|
21
|
+
}
|
|
19
22
|
},
|
|
20
23
|
url
|
|
21
24
|
});
|
|
@@ -80,19 +83,26 @@ async function postRobot(url, { expiresAt, ...body }, query = {}) {
|
|
|
80
83
|
const { organizationId, ...body } = options;
|
|
81
84
|
return postRobot(`/access/organization/${organizationId}/robots`, body);
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
*/ export async function deleteToken(options) {
|
|
91
|
-
const { projectId, tokenId } = options;
|
|
86
|
+
function robotsUrl({ resourceId, resourceType }) {
|
|
87
|
+
return `/access/${resourceType}/${resourceId}/robots`;
|
|
88
|
+
}
|
|
89
|
+
function isManagedByOrganization(robot, organizationId) {
|
|
90
|
+
return robot.managedBy?.resourceType === 'organization' && robot.managedBy.resourceId === organizationId;
|
|
91
|
+
}
|
|
92
|
+
/** Delete a token from the requested scope. @internal */ export async function deleteToken(scope, tokenId) {
|
|
92
93
|
const client = await getClient();
|
|
94
|
+
const url = `${robotsUrl(scope)}/${tokenId}`;
|
|
95
|
+
if (scope.resourceType === 'organization') {
|
|
96
|
+
const robot = await client.request({
|
|
97
|
+
url
|
|
98
|
+
});
|
|
99
|
+
if (!isManagedByOrganization(robot, scope.resourceId)) {
|
|
100
|
+
throw new Error(`Token "${tokenId}" is not managed by organization "${scope.resourceId}". Use tokens list --organization ${scope.resourceId} to find tokens managed by this organization.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
93
103
|
return client.request({
|
|
94
104
|
method: 'DELETE',
|
|
95
|
-
url
|
|
105
|
+
url
|
|
96
106
|
});
|
|
97
107
|
}
|
|
98
108
|
/**
|
|
@@ -110,16 +120,16 @@ async function postRobot(url, { expiresAt, ...body }, query = {}) {
|
|
|
110
120
|
url: '/access/robots/me/rotate'
|
|
111
121
|
});
|
|
112
122
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Get all tokens for a project. Tokens managed by an organization are
|
|
115
|
-
* excluded: they cannot be managed at the project scope.
|
|
116
|
-
* @param projectId - The project ID
|
|
117
|
-
* @returns A promise that resolves to an array of robots
|
|
118
|
-
*
|
|
119
|
-
* @internal
|
|
120
|
-
*/ export async function getTokens(projectId) {
|
|
123
|
+
/** List tokens managed at the requested scope. @internal */ export async function getTokens(scope) {
|
|
121
124
|
const client = await getClient();
|
|
122
|
-
const
|
|
125
|
+
const query = scope.resourceType === 'organization' ? {
|
|
126
|
+
includeChildren: 'true',
|
|
127
|
+
limit: '500'
|
|
128
|
+
} : {};
|
|
129
|
+
const robots = await fetchAllPages(client, robotsUrl(scope), query);
|
|
130
|
+
if (scope.resourceType === 'organization') {
|
|
131
|
+
return robots.filter((robot)=>isManagedByOrganization(robot, scope.resourceId));
|
|
132
|
+
}
|
|
123
133
|
return robots.filter((robot)=>robot.managedBy?.resourceType !== 'organization');
|
|
124
134
|
}
|
|
125
135
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type SanityClient} from '@sanity/client'\n\nimport {TOKENS_API_VERSION} from '../actions/tokens/constants.js'\nimport {\n type Membership,\n type Robot,\n type RobotWithToken,\n type Role,\n} from '../actions/tokens/types.js'\n\ninterface PaginatedResponse<T> {\n data: T[]\n nextCursor: string | null\n}\n\nfunction getClient(token?: string): Promise<SanityClient> {\n return getGlobalCliClient({\n apiVersion: TOKENS_API_VERSION,\n requireUser: true,\n ...(token === undefined ? {} : {token}),\n })\n}\n\nasync function fetchAllPages<T>(client: SanityClient
|
|
1
|
+
{"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type SanityClient} from '@sanity/client'\n\nimport {TOKENS_API_VERSION} from '../actions/tokens/constants.js'\nimport {\n type Membership,\n type Robot,\n type RobotWithToken,\n type Role,\n type TokenScope,\n} from '../actions/tokens/types.js'\n\ninterface PaginatedResponse<T> {\n data: T[]\n nextCursor: string | null\n}\n\nfunction getClient(token?: string): Promise<SanityClient> {\n return getGlobalCliClient({\n apiVersion: TOKENS_API_VERSION,\n requireUser: true,\n ...(token === undefined ? {} : {token}),\n })\n}\n\nasync function fetchAllPages<T>(\n client: SanityClient,\n url: string,\n query: Record<string, string> = {},\n): Promise<T[]> {\n const items: T[] = []\n let cursor: string | null = null\n\n do {\n const response: PaginatedResponse<T> = await client.request<PaginatedResponse<T>>({\n query: {...query, ...(cursor === null ? {} : {nextCursor: cursor})},\n url,\n })\n items.push(...response.data)\n cursor = response.nextCursor\n } while (cursor !== null)\n\n return items\n}\n\n/**\n * Get the membership granting a robot access to the given project\n * @param robot - The robot to inspect\n * @param projectId - The project ID\n * @returns The project membership, if any\n *\n * @internal\n */\nexport function getProjectMembership(robot: Robot, projectId: string): Membership | undefined {\n return robot.memberships.find(\n (membership) => membership.resourceType === 'project' && membership.resourceId === projectId,\n )\n}\n\ninterface RobotBody {\n label: string\n memberships: Membership[]\n\n expiresAt?: string\n}\n\nasync function postRobot(\n url: string,\n {expiresAt, ...body}: RobotBody,\n query: Record<string, string> = {},\n): Promise<RobotWithToken> {\n const client = await getClient()\n\n return client.request<RobotWithToken>({\n body: {...body, ...(expiresAt === undefined ? {} : {expiresAt})},\n method: 'POST',\n query,\n url,\n })\n}\n\ninterface CreateTokenOptions {\n label: string\n projectId: string\n roleNames: string[]\n\n expiresAt?: string\n sendNotification?: boolean\n}\n\n/**\n * Add a token to a project\n * @param options - The options for adding a token to a project\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createToken(options: CreateTokenOptions): Promise<RobotWithToken> {\n const {expiresAt, label, projectId, roleNames, sendNotification} = options\n\n return postRobot(\n `/access/project/${projectId}/robots`,\n {\n expiresAt,\n label,\n memberships: [{resourceId: projectId, resourceType: 'project', roleNames}],\n },\n sendNotification === false ? {sendNotification: 'false'} : {},\n )\n}\n\ninterface CreateOrganizationTokenOptions extends RobotBody {\n organizationId: string\n}\n\n/**\n * Create an organization token, which can hold organization permissions and roles on several projects\n * @param options - The options for the organization token\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createOrganizationToken(\n options: CreateOrganizationTokenOptions,\n): Promise<RobotWithToken> {\n const {organizationId, ...body} = options\n\n return postRobot(`/access/organization/${organizationId}/robots`, body)\n}\n\nfunction robotsUrl({resourceId, resourceType}: TokenScope): string {\n return `/access/${resourceType}/${resourceId}/robots`\n}\n\nfunction isManagedByOrganization(robot: Robot, organizationId: string): boolean {\n return (\n robot.managedBy?.resourceType === 'organization' &&\n robot.managedBy.resourceId === organizationId\n )\n}\n\n/** Delete a token from the requested scope. @internal */\nexport async function deleteToken(scope: TokenScope, tokenId: string): Promise<void> {\n const client = await getClient()\n const url = `${robotsUrl(scope)}/${tokenId}`\n\n if (scope.resourceType === 'organization') {\n const robot = await client.request<Robot>({url})\n if (!isManagedByOrganization(robot, scope.resourceId)) {\n throw new Error(\n `Token \"${tokenId}\" is not managed by organization \"${scope.resourceId}\". Use tokens list --organization ${scope.resourceId} to find tokens managed by this organization.`,\n )\n }\n }\n\n return client.request({method: 'DELETE', url})\n}\n\n/**\n * Rotate a robot token, replacing its secret while preserving the robot,\n * its roles, and the token's expiry. The request authenticates as the robot\n * itself; the previous secret is revoked immediately.\n * @param token - The current robot token secret\n * @returns A promise that resolves to the robot with its new secret token\n *\n * @internal\n */\nexport async function rotateToken(token: string): Promise<RobotWithToken> {\n const client = await getClient(token)\n\n return client.request<RobotWithToken>({\n method: 'POST',\n url: '/access/robots/me/rotate',\n })\n}\n\n/** List tokens managed at the requested scope. @internal */\nexport async function getTokens(scope: TokenScope): Promise<Robot[]> {\n const client = await getClient()\n const query: Record<string, string> =\n scope.resourceType === 'organization' ? {includeChildren: 'true', limit: '500'} : {}\n const robots = await fetchAllPages<Robot>(client, robotsUrl(scope), query)\n\n if (scope.resourceType === 'organization') {\n return robots.filter((robot) => isManagedByOrganization(robot, scope.resourceId))\n }\n\n return robots.filter((robot) => robot.managedBy?.resourceType !== 'organization')\n}\n\n/**\n * Get all roles for a project\n * @param projectId - The project ID\n * @returns A promise that resolves to an array of project roles\n *\n * @internal\n */\nexport async function getTokenRoles(projectId: string): Promise<Role[]> {\n const client = await getClient()\n\n return fetchAllPages<Role>(client, `/access/project/${projectId}/roles`)\n}\n"],"names":["getGlobalCliClient","TOKENS_API_VERSION","getClient","token","apiVersion","requireUser","undefined","fetchAllPages","client","url","query","items","cursor","response","request","nextCursor","push","data","getProjectMembership","robot","projectId","memberships","find","membership","resourceType","resourceId","postRobot","expiresAt","body","method","createToken","options","label","roleNames","sendNotification","createOrganizationToken","organizationId","robotsUrl","isManagedByOrganization","managedBy","deleteToken","scope","tokenId","Error","rotateToken","getTokens","includeChildren","limit","robots","filter","getTokenRoles"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { promptForProject } from '../prompts/promptForProject.js';
|
|
2
|
+
export async function resolveTokenScope(options) {
|
|
3
|
+
const { getProjectId, grant, organization } = options;
|
|
4
|
+
if (organization) return {
|
|
5
|
+
resourceId: organization,
|
|
6
|
+
resourceType: 'organization'
|
|
7
|
+
};
|
|
8
|
+
const projectId = await getProjectId({
|
|
9
|
+
fallback: ()=>promptForProject({
|
|
10
|
+
requiredPermissions: [
|
|
11
|
+
{
|
|
12
|
+
grant,
|
|
13
|
+
permission: 'sanity.project.tokens'
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
resourceId: projectId,
|
|
20
|
+
resourceType: 'project'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=resolveTokenScope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/resolveTokenScope.ts"],"sourcesContent":["import {type SanityCommandInterface} from '@sanity/cli-core'\n\nimport {type TokenScope} from '../actions/tokens/types.js'\nimport {promptForProject} from '../prompts/promptForProject.js'\n\nexport async function resolveTokenScope(options: {\n getProjectId: SanityCommandInterface['getProjectId']\n grant: 'delete' | 'read'\n organization?: string\n}): Promise<TokenScope> {\n const {getProjectId, grant, organization} = options\n if (organization) return {resourceId: organization, resourceType: 'organization'}\n\n const projectId = await getProjectId({\n fallback: () =>\n promptForProject({requiredPermissions: [{grant, permission: 'sanity.project.tokens'}]}),\n })\n return {resourceId: projectId, resourceType: 'project'}\n}\n"],"names":["promptForProject","resolveTokenScope","options","getProjectId","grant","organization","resourceId","resourceType","projectId","fallback","requiredPermissions","permission"],"mappings":"AAGA,SAAQA,gBAAgB,QAAO,iCAAgC;AAE/D,OAAO,eAAeC,kBAAkBC,OAIvC;IACC,MAAM,EAACC,YAAY,EAAEC,KAAK,EAAEC,YAAY,EAAC,GAAGH;IAC5C,IAAIG,cAAc,OAAO;QAACC,YAAYD;QAAcE,cAAc;IAAc;IAEhF,MAAMC,YAAY,MAAML,aAAa;QACnCM,UAAU,IACRT,iBAAiB;gBAACU,qBAAqB;oBAAC;wBAACN;wBAAOO,YAAY;oBAAuB;iBAAE;YAAA;IACzF;IACA,OAAO;QAACL,YAAYE;QAAWD,cAAc;IAAS;AACxD"}
|