@sanity/cli 8.19.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/actions/whoami/resolveSSOProvider.js +49 -0
- package/dist/actions/whoami/resolveSSOProvider.js.map +1 -0
- 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/commands/whoami.js +110 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +3 -1
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/services/auth.js +14 -0
- package/dist/services/auth.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 +1162 -1092
- 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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { subdebug } from '@sanity/cli-core';
|
|
2
|
+
import { getOrganizationSSOProviders } from '../../services/auth.js';
|
|
3
|
+
import { listOrganizations } from '../../services/organizations.js';
|
|
4
|
+
const debug = subdebug('whoami:sso');
|
|
5
|
+
const SAML_PREFIX = 'saml-';
|
|
6
|
+
/**
|
|
7
|
+
* Get the SSO provider ID from a user's `provider` value (`saml-<providerId>`).
|
|
8
|
+
*
|
|
9
|
+
* @param provider - The provider value from `/users/me`
|
|
10
|
+
* @returns The SSO provider ID, or undefined if the user did not log in with SAML
|
|
11
|
+
* @internal
|
|
12
|
+
*/ export function getSamlProviderId(provider) {
|
|
13
|
+
if (!provider.startsWith(SAML_PREFIX)) return undefined;
|
|
14
|
+
return provider.slice(SAML_PREFIX.length) || undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Find the organization and SSO provider behind a `saml-<providerId>` login, by
|
|
18
|
+
* looking through the SSO providers of the organizations the user belongs to.
|
|
19
|
+
*
|
|
20
|
+
* Best effort: returns undefined if the provider cannot be found or the lookup
|
|
21
|
+
* fails, so callers can still show that the user logged in with SAML.
|
|
22
|
+
*
|
|
23
|
+
* @param providerId - The SSO provider ID
|
|
24
|
+
* @returns The provider and organization, or undefined if it could not be resolved
|
|
25
|
+
* @internal
|
|
26
|
+
*/ export async function resolveSSOProvider(providerId) {
|
|
27
|
+
try {
|
|
28
|
+
const organizations = await listOrganizations();
|
|
29
|
+
for (const organization of organizations){
|
|
30
|
+
const providers = await getOrganizationSSOProviders(organization.id);
|
|
31
|
+
const provider = providers.find((candidate)=>candidate.id === providerId);
|
|
32
|
+
if (provider) {
|
|
33
|
+
return {
|
|
34
|
+
organizationId: organization.id,
|
|
35
|
+
organizationName: organization.name,
|
|
36
|
+
organizationSlug: organization.slug,
|
|
37
|
+
providerId,
|
|
38
|
+
providerName: provider.name
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
debug('SSO provider %s not found in any organization', providerId);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
debug('Failed to resolve SSO provider %s: %O', providerId, err);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=resolveSSOProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/whoami/resolveSSOProvider.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\n\nimport {getOrganizationSSOProviders} from '../../services/auth.js'\nimport {listOrganizations} from '../../services/organizations.js'\n\nconst debug = subdebug('whoami:sso')\n\nconst SAML_PREFIX = 'saml-'\n\n/**\n * The SSO provider and organization a SAML user logged in through.\n *\n * @internal\n */\nexport interface SSOProviderInfo {\n organizationId: string\n organizationName: string\n organizationSlug: string | null\n providerId: string\n providerName: string\n}\n\n/**\n * Get the SSO provider ID from a user's `provider` value (`saml-<providerId>`).\n *\n * @param provider - The provider value from `/users/me`\n * @returns The SSO provider ID, or undefined if the user did not log in with SAML\n * @internal\n */\nexport function getSamlProviderId(provider: string): string | undefined {\n if (!provider.startsWith(SAML_PREFIX)) return undefined\n return provider.slice(SAML_PREFIX.length) || undefined\n}\n\n/**\n * Find the organization and SSO provider behind a `saml-<providerId>` login, by\n * looking through the SSO providers of the organizations the user belongs to.\n *\n * Best effort: returns undefined if the provider cannot be found or the lookup\n * fails, so callers can still show that the user logged in with SAML.\n *\n * @param providerId - The SSO provider ID\n * @returns The provider and organization, or undefined if it could not be resolved\n * @internal\n */\nexport async function resolveSSOProvider(providerId: string): Promise<SSOProviderInfo | undefined> {\n try {\n const organizations = await listOrganizations()\n for (const organization of organizations) {\n const providers = await getOrganizationSSOProviders(organization.id)\n const provider = providers.find((candidate) => candidate.id === providerId)\n if (provider) {\n return {\n organizationId: organization.id,\n organizationName: organization.name,\n organizationSlug: organization.slug,\n providerId,\n providerName: provider.name,\n }\n }\n }\n debug('SSO provider %s not found in any organization', providerId)\n } catch (err) {\n debug('Failed to resolve SSO provider %s: %O', providerId, err)\n }\n return undefined\n}\n"],"names":["subdebug","getOrganizationSSOProviders","listOrganizations","debug","SAML_PREFIX","getSamlProviderId","provider","startsWith","undefined","slice","length","resolveSSOProvider","providerId","organizations","organization","providers","id","find","candidate","organizationId","organizationName","name","organizationSlug","slug","providerName","err"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AAEzC,SAAQC,2BAA2B,QAAO,yBAAwB;AAClE,SAAQC,iBAAiB,QAAO,kCAAiC;AAEjE,MAAMC,QAAQH,SAAS;AAEvB,MAAMI,cAAc;AAepB;;;;;;CAMC,GACD,OAAO,SAASC,kBAAkBC,QAAgB;IAChD,IAAI,CAACA,SAASC,UAAU,CAACH,cAAc,OAAOI;IAC9C,OAAOF,SAASG,KAAK,CAACL,YAAYM,MAAM,KAAKF;AAC/C;AAEA;;;;;;;;;;CAUC,GACD,OAAO,eAAeG,mBAAmBC,UAAkB;IACzD,IAAI;QACF,MAAMC,gBAAgB,MAAMX;QAC5B,KAAK,MAAMY,gBAAgBD,cAAe;YACxC,MAAME,YAAY,MAAMd,4BAA4Ba,aAAaE,EAAE;YACnE,MAAMV,WAAWS,UAAUE,IAAI,CAAC,CAACC,YAAcA,UAAUF,EAAE,KAAKJ;YAChE,IAAIN,UAAU;gBACZ,OAAO;oBACLa,gBAAgBL,aAAaE,EAAE;oBAC/BI,kBAAkBN,aAAaO,IAAI;oBACnCC,kBAAkBR,aAAaS,IAAI;oBACnCX;oBACAY,cAAclB,SAASe,IAAI;gBAC7B;YACF;QACF;QACAlB,MAAM,iDAAiDS;IACzD,EAAE,OAAOa,KAAK;QACZtB,MAAM,yCAAyCS,YAAYa;IAC7D;IACA,OAAOjB;AACT"}
|
|
@@ -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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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { exitCodes, getCliTokenInfo, SanityCommand } from '@sanity/cli-core';
|
|
3
|
+
import { isHttpError } from '@sanity/client';
|
|
4
|
+
import { getProviderName } from '../actions/auth/getProviderName.js';
|
|
5
|
+
import { formatKeyValue } from '../actions/debug/output.js';
|
|
6
|
+
import { getSamlProviderId, resolveSSOProvider } from '../actions/whoami/resolveSSOProvider.js';
|
|
7
|
+
import { getCliUser } from '../services/user.js';
|
|
8
|
+
export class WhoamiCommand extends SanityCommand {
|
|
9
|
+
static description = 'Show the user or API token the CLI is authenticated as';
|
|
10
|
+
static examples = [
|
|
11
|
+
{
|
|
12
|
+
command: '<%= config.bin %> <%= command.id %>',
|
|
13
|
+
description: 'Show the currently authenticated user'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
command: '<%= config.bin %> <%= command.id %> --json',
|
|
17
|
+
description: 'Output the current user as JSON, for scripts and agents'
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
json: Flags.boolean({
|
|
22
|
+
default: false,
|
|
23
|
+
description: 'Output as JSON'
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(WhoamiCommand);
|
|
28
|
+
const tokenInfo = await getCliTokenInfo();
|
|
29
|
+
if (!tokenInfo?.token) {
|
|
30
|
+
this.error('Not logged in. Run `sanity login` to authenticate.', {
|
|
31
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const user = await getCliUser().catch((err)=>{
|
|
35
|
+
// A stale SANITY_AUTH_TOKEN overrides a valid saved login, so "run sanity login" won't help
|
|
36
|
+
if (tokenInfo.source === ENV_TOKEN_SOURCE && isRejectedToken(err)) {
|
|
37
|
+
this.error('Authentication failed using SANITY_AUTH_TOKEN.\nUnset it to use your saved login, or replace it with a valid token.', {
|
|
38
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
});
|
|
43
|
+
const samlProviderId = getSamlProviderId(user.provider);
|
|
44
|
+
const sso = samlProviderId ? await resolveSSOProvider(samlProviderId) : undefined;
|
|
45
|
+
if (flags.json) {
|
|
46
|
+
this.log(JSON.stringify({
|
|
47
|
+
email: user.email ?? null,
|
|
48
|
+
id: user.id,
|
|
49
|
+
name: user.name,
|
|
50
|
+
provider: user.provider,
|
|
51
|
+
...samlProviderId ? {
|
|
52
|
+
sso: sso ?? null
|
|
53
|
+
} : {},
|
|
54
|
+
tokenSource: tokenInfo.source
|
|
55
|
+
}, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const padTo = 8 // "Provider" is the longest key
|
|
59
|
+
;
|
|
60
|
+
this.log(formatKeyValue('Name', user.name, {
|
|
61
|
+
indent: 0,
|
|
62
|
+
padTo
|
|
63
|
+
}));
|
|
64
|
+
if (user.email) {
|
|
65
|
+
this.log(formatKeyValue('Email', user.email, {
|
|
66
|
+
indent: 0,
|
|
67
|
+
padTo
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
this.log(formatKeyValue('ID', user.id, {
|
|
71
|
+
indent: 0,
|
|
72
|
+
padTo
|
|
73
|
+
}));
|
|
74
|
+
this.log(formatKeyValue('Provider', formatProvider(user.provider, samlProviderId, sso), {
|
|
75
|
+
indent: 0,
|
|
76
|
+
padTo
|
|
77
|
+
}));
|
|
78
|
+
if (sso) {
|
|
79
|
+
this.log(formatKeyValue('Org', formatOrganization(sso), {
|
|
80
|
+
indent: 0,
|
|
81
|
+
padTo
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
this.log(formatKeyValue('Token', tokenInfo.source, {
|
|
85
|
+
indent: 0,
|
|
86
|
+
padTo
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const ENV_TOKEN_SOURCE = 'SANITY_AUTH_TOKEN environment variable';
|
|
91
|
+
function isRejectedToken(err) {
|
|
92
|
+
return isHttpError(err) && (err.statusCode === 401 || err.statusCode === 403);
|
|
93
|
+
}
|
|
94
|
+
function formatProvider(provider, samlProviderId, sso) {
|
|
95
|
+
if (provider === 'sanity-token') return 'API token';
|
|
96
|
+
if (samlProviderId) return formatSamlProvider(sso);
|
|
97
|
+
return getProviderName(provider);
|
|
98
|
+
}
|
|
99
|
+
function formatOrganization(sso) {
|
|
100
|
+
if (!sso.organizationSlug) return sso.organizationName;
|
|
101
|
+
return `${sso.organizationName} (sanity login --sso ${sso.organizationSlug})`;
|
|
102
|
+
}
|
|
103
|
+
function formatSamlProvider(sso) {
|
|
104
|
+
// Provider names like "saml" or "SSO" repeat the label without adding anything
|
|
105
|
+
const name = sso?.providerName.trim();
|
|
106
|
+
if (!name || /^(saml|sso|saml sso)$/i.test(name)) return 'SAML SSO';
|
|
107
|
+
return `SAML SSO (${name})`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {exitCodes, getCliTokenInfo, SanityCommand} from '@sanity/cli-core'\nimport {isHttpError} from '@sanity/client'\n\nimport {getProviderName} from '../actions/auth/getProviderName.js'\nimport {formatKeyValue} from '../actions/debug/output.js'\nimport {\n getSamlProviderId,\n resolveSSOProvider,\n type SSOProviderInfo,\n} from '../actions/whoami/resolveSSOProvider.js'\nimport {getCliUser} from '../services/user.js'\n\nexport class WhoamiCommand extends SanityCommand<typeof WhoamiCommand> {\n static override description = 'Show the user or API token the CLI is authenticated as'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Show the currently authenticated user',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json',\n description: 'Output the current user as JSON, for scripts and agents',\n },\n ]\n\n static override flags = {\n json: Flags.boolean({\n default: false,\n description: 'Output as JSON',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(WhoamiCommand)\n\n const tokenInfo = await getCliTokenInfo()\n if (!tokenInfo?.token) {\n this.error('Not logged in. Run `sanity login` to authenticate.', {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n const user = await getCliUser().catch((err: unknown) => {\n // A stale SANITY_AUTH_TOKEN overrides a valid saved login, so \"run sanity login\" won't help\n if (tokenInfo.source === ENV_TOKEN_SOURCE && isRejectedToken(err)) {\n this.error(\n 'Authentication failed using SANITY_AUTH_TOKEN.\\nUnset it to use your saved login, or replace it with a valid token.',\n {exit: exitCodes.RUNTIME_ERROR},\n )\n }\n throw err\n })\n const samlProviderId = getSamlProviderId(user.provider)\n const sso = samlProviderId ? await resolveSSOProvider(samlProviderId) : undefined\n\n if (flags.json) {\n this.log(\n JSON.stringify(\n {\n email: user.email ?? null,\n id: user.id,\n name: user.name,\n provider: user.provider,\n ...(samlProviderId ? {sso: sso ?? null} : {}),\n tokenSource: tokenInfo.source,\n },\n null,\n 2,\n ),\n )\n return\n }\n\n const padTo = 8 // \"Provider\" is the longest key\n this.log(formatKeyValue('Name', user.name, {indent: 0, padTo}))\n if (user.email) {\n this.log(formatKeyValue('Email', user.email, {indent: 0, padTo}))\n }\n this.log(formatKeyValue('ID', user.id, {indent: 0, padTo}))\n this.log(\n formatKeyValue('Provider', formatProvider(user.provider, samlProviderId, sso), {\n indent: 0,\n padTo,\n }),\n )\n if (sso) {\n this.log(formatKeyValue('Org', formatOrganization(sso), {indent: 0, padTo}))\n }\n this.log(formatKeyValue('Token', tokenInfo.source, {indent: 0, padTo}))\n }\n}\n\nconst ENV_TOKEN_SOURCE = 'SANITY_AUTH_TOKEN environment variable'\n\nfunction isRejectedToken(err: unknown): boolean {\n return isHttpError(err) && (err.statusCode === 401 || err.statusCode === 403)\n}\n\nfunction formatProvider(\n provider: string,\n samlProviderId: string | undefined,\n sso: SSOProviderInfo | undefined,\n): string {\n if (provider === 'sanity-token') return 'API token'\n if (samlProviderId) return formatSamlProvider(sso)\n return getProviderName(provider)\n}\n\nfunction formatOrganization(sso: SSOProviderInfo): string {\n if (!sso.organizationSlug) return sso.organizationName\n return `${sso.organizationName} (sanity login --sso ${sso.organizationSlug})`\n}\n\nfunction formatSamlProvider(sso: SSOProviderInfo | undefined): string {\n // Provider names like \"saml\" or \"SSO\" repeat the label without adding anything\n const name = sso?.providerName.trim()\n if (!name || /^(saml|sso|saml sso)$/i.test(name)) return 'SAML SSO'\n return `SAML SSO (${name})`\n}\n"],"names":["Flags","exitCodes","getCliTokenInfo","SanityCommand","isHttpError","getProviderName","formatKeyValue","getSamlProviderId","resolveSSOProvider","getCliUser","WhoamiCommand","description","examples","command","flags","json","boolean","default","run","parse","tokenInfo","token","error","exit","RUNTIME_ERROR","user","catch","err","source","ENV_TOKEN_SOURCE","isRejectedToken","samlProviderId","provider","sso","undefined","log","JSON","stringify","email","id","name","tokenSource","padTo","indent","formatProvider","formatOrganization","statusCode","formatSamlProvider","organizationSlug","organizationName","providerName","trim","test"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AACjC,SAAQC,SAAS,EAAEC,eAAe,EAAEC,aAAa,QAAO,mBAAkB;AAC1E,SAAQC,WAAW,QAAO,iBAAgB;AAE1C,SAAQC,eAAe,QAAO,qCAAoC;AAClE,SAAQC,cAAc,QAAO,6BAA4B;AACzD,SACEC,iBAAiB,EACjBC,kBAAkB,QAEb,0CAAyC;AAChD,SAAQC,UAAU,QAAO,sBAAqB;AAE9C,OAAO,MAAMC,sBAAsBP;IACjC,OAAgBQ,cAAc,yDAAwD;IAEtF,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtBC,MAAMf,MAAMgB,OAAO,CAAC;YAClBC,SAAS;YACTN,aAAa;QACf;IACF,EAAC;IAED,MAAaO,MAAqB;QAChC,MAAM,EAACJ,KAAK,EAAC,GAAG,MAAM,IAAI,CAACK,KAAK,CAACT;QAEjC,MAAMU,YAAY,MAAMlB;QACxB,IAAI,CAACkB,WAAWC,OAAO;YACrB,IAAI,CAACC,KAAK,CAAC,sDAAsD;gBAC/DC,MAAMtB,UAAUuB,aAAa;YAC/B;QACF;QAEA,MAAMC,OAAO,MAAMhB,aAAaiB,KAAK,CAAC,CAACC;YACrC,4FAA4F;YAC5F,IAAIP,UAAUQ,MAAM,KAAKC,oBAAoBC,gBAAgBH,MAAM;gBACjE,IAAI,CAACL,KAAK,CACR,uHACA;oBAACC,MAAMtB,UAAUuB,aAAa;gBAAA;YAElC;YACA,MAAMG;QACR;QACA,MAAMI,iBAAiBxB,kBAAkBkB,KAAKO,QAAQ;QACtD,MAAMC,MAAMF,iBAAiB,MAAMvB,mBAAmBuB,kBAAkBG;QAExE,IAAIpB,MAAMC,IAAI,EAAE;YACd,IAAI,CAACoB,GAAG,CACNC,KAAKC,SAAS,CACZ;gBACEC,OAAOb,KAAKa,KAAK,IAAI;gBACrBC,IAAId,KAAKc,EAAE;gBACXC,MAAMf,KAAKe,IAAI;gBACfR,UAAUP,KAAKO,QAAQ;gBACvB,GAAID,iBAAiB;oBAACE,KAAKA,OAAO;gBAAI,IAAI,CAAC,CAAC;gBAC5CQ,aAAarB,UAAUQ,MAAM;YAC/B,GACA,MACA;YAGJ;QACF;QAEA,MAAMc,QAAQ,EAAE,gCAAgC;;QAChD,IAAI,CAACP,GAAG,CAAC7B,eAAe,QAAQmB,KAAKe,IAAI,EAAE;YAACG,QAAQ;YAAGD;QAAK;QAC5D,IAAIjB,KAAKa,KAAK,EAAE;YACd,IAAI,CAACH,GAAG,CAAC7B,eAAe,SAASmB,KAAKa,KAAK,EAAE;gBAACK,QAAQ;gBAAGD;YAAK;QAChE;QACA,IAAI,CAACP,GAAG,CAAC7B,eAAe,MAAMmB,KAAKc,EAAE,EAAE;YAACI,QAAQ;YAAGD;QAAK;QACxD,IAAI,CAACP,GAAG,CACN7B,eAAe,YAAYsC,eAAenB,KAAKO,QAAQ,EAAED,gBAAgBE,MAAM;YAC7EU,QAAQ;YACRD;QACF;QAEF,IAAIT,KAAK;YACP,IAAI,CAACE,GAAG,CAAC7B,eAAe,OAAOuC,mBAAmBZ,MAAM;gBAACU,QAAQ;gBAAGD;YAAK;QAC3E;QACA,IAAI,CAACP,GAAG,CAAC7B,eAAe,SAASc,UAAUQ,MAAM,EAAE;YAACe,QAAQ;YAAGD;QAAK;IACtE;AACF;AAEA,MAAMb,mBAAmB;AAEzB,SAASC,gBAAgBH,GAAY;IACnC,OAAOvB,YAAYuB,QAASA,CAAAA,IAAImB,UAAU,KAAK,OAAOnB,IAAImB,UAAU,KAAK,GAAE;AAC7E;AAEA,SAASF,eACPZ,QAAgB,EAChBD,cAAkC,EAClCE,GAAgC;IAEhC,IAAID,aAAa,gBAAgB,OAAO;IACxC,IAAID,gBAAgB,OAAOgB,mBAAmBd;IAC9C,OAAO5B,gBAAgB2B;AACzB;AAEA,SAASa,mBAAmBZ,GAAoB;IAC9C,IAAI,CAACA,IAAIe,gBAAgB,EAAE,OAAOf,IAAIgB,gBAAgB;IACtD,OAAO,GAAGhB,IAAIgB,gBAAgB,CAAC,qBAAqB,EAAEhB,IAAIe,gBAAgB,CAAC,CAAC,CAAC;AAC/E;AAEA,SAASD,mBAAmBd,GAAgC;IAC1D,+EAA+E;IAC/E,MAAMO,OAAOP,KAAKiB,aAAaC;IAC/B,IAAI,CAACX,QAAQ,yBAAyBY,IAAI,CAACZ,OAAO,OAAO;IACzD,OAAO,CAAC,UAAU,EAAEA,KAAK,CAAC,CAAC;AAC7B"}
|