mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(console): support assign organization resource scopes for 3rd-party app (#5812)
This commit is contained in:
parent
ae5284b1d3
commit
3227f61fad
31 changed files with 830 additions and 351 deletions
|
@ -8,7 +8,7 @@ import type { Props as TextLinkProps } from '@/ds-components/TextLink';
|
|||
import FormCardLayout from './FormCardLayout';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
export type Props = {
|
||||
readonly title: AdminConsoleKey;
|
||||
readonly tag?: ReactNode;
|
||||
readonly description?: AdminConsoleKey;
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { ApplicationUserConsentScopeType } from '@logto/schemas';
|
||||
|
||||
export const permissionTabs = Object.freeze({
|
||||
[ApplicationUserConsentScopeType.UserScopes]: {
|
||||
title: 'application_details.permissions.user_profile',
|
||||
key: ApplicationUserConsentScopeType.UserScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.ResourceScopes]: {
|
||||
title: 'application_details.permissions.api_resource',
|
||||
key: ApplicationUserConsentScopeType.ResourceScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.OrganizationResourceScopes]: {
|
||||
// TODO @xiaoyijun: update the title
|
||||
title: 'application_details.permissions.api_resource',
|
||||
key: ApplicationUserConsentScopeType.OrganizationResourceScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.OrganizationScopes]: {
|
||||
title: 'application_details.permissions.organization',
|
||||
key: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
},
|
||||
}) satisfies {
|
||||
[key in ApplicationUserConsentScopeType]: {
|
||||
title: AdminConsoleKey;
|
||||
key: key;
|
||||
};
|
||||
};
|
|
@ -1,114 +0,0 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { ApplicationUserConsentScopeType } from '@logto/schemas';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ConfirmModal from '@/ds-components/ConfirmModal';
|
||||
import DataTransferBox from '@/ds-components/DataTransferBox';
|
||||
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
||||
import TabWrapper from '@/ds-components/TabWrapper';
|
||||
|
||||
import { permissionTabs } from './constants';
|
||||
import useApplicationScopesAssignment from './use-application-scopes-assignment';
|
||||
|
||||
const modalText = Object.freeze({
|
||||
title: 'application_details.permissions.table_name',
|
||||
subtitle: 'application_details.permissions.permissions_assignment_description',
|
||||
saveBtn: 'general.save',
|
||||
}) satisfies Record<string, AdminConsoleKey>;
|
||||
|
||||
type Props = {
|
||||
readonly isOpen: boolean;
|
||||
readonly onClose: () => void;
|
||||
readonly applicationId: string;
|
||||
};
|
||||
|
||||
function ApplicationScopesAssignmentModal({ isOpen, onClose, applicationId }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { activeTab, setActiveTab, scopesAssignment, clearSelectedData, onSubmit, isLoading } =
|
||||
useApplicationScopesAssignment(applicationId);
|
||||
|
||||
const onCloseHandler = useCallback(() => {
|
||||
onClose();
|
||||
clearSelectedData();
|
||||
setActiveTab(ApplicationUserConsentScopeType.UserScopes);
|
||||
}, [clearSelectedData, onClose, setActiveTab]);
|
||||
|
||||
const onSubmitHandler = useCallback(async () => {
|
||||
await onSubmit();
|
||||
onCloseHandler();
|
||||
}, [onCloseHandler, onSubmit]);
|
||||
|
||||
// If any of the tabs has selected scopes, the modal is dirty
|
||||
const isDirty = Object.values(scopesAssignment).some(
|
||||
({ selectedData }) => selectedData.length > 0
|
||||
);
|
||||
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
Object.values(permissionTabs)
|
||||
/**
|
||||
* Hide the organization resource scopes tab since the feature is not ready.
|
||||
* We don't need the `isDevFeaturesEnabled` flag since the feature will change the UI.
|
||||
* Todo @xiaoyijun Implement the new organization resource scopes feature. LOG-8823
|
||||
*/
|
||||
.filter(({ key }) => key !== ApplicationUserConsentScopeType.OrganizationResourceScopes)
|
||||
.map(({ title, key }) => {
|
||||
const selectedDataCount = scopesAssignment[key].selectedData.length;
|
||||
|
||||
return (
|
||||
<TabNavItem
|
||||
key={key}
|
||||
isActive={key === activeTab}
|
||||
onClick={() => {
|
||||
setActiveTab(key);
|
||||
}}
|
||||
>
|
||||
{`${t(title)}${selectedDataCount ? ` (${selectedDataCount})` : ''}`}
|
||||
</TabNavItem>
|
||||
);
|
||||
}),
|
||||
[activeTab, scopesAssignment, setActiveTab, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
isLoading={isLoading}
|
||||
title={modalText.title}
|
||||
subtitle={modalText.subtitle}
|
||||
isConfirmButtonDisabled={!isDirty}
|
||||
confirmButtonType="primary"
|
||||
confirmButtonText={modalText.saveBtn}
|
||||
isCancelButtonVisible={false}
|
||||
size="large"
|
||||
onCancel={onCloseHandler}
|
||||
onConfirm={onSubmitHandler}
|
||||
>
|
||||
<TabNav>{tabs}</TabNav>
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.UserScopes}
|
||||
isActive={ApplicationUserConsentScopeType.UserScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox {...scopesAssignment[ApplicationUserConsentScopeType.UserScopes]} />
|
||||
</TabWrapper>
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.ResourceScopes}
|
||||
isActive={ApplicationUserConsentScopeType.ResourceScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox {...scopesAssignment[ApplicationUserConsentScopeType.ResourceScopes]} />
|
||||
</TabWrapper>
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.OrganizationScopes}
|
||||
isActive={ApplicationUserConsentScopeType.OrganizationScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox
|
||||
{...scopesAssignment[ApplicationUserConsentScopeType.OrganizationScopes]}
|
||||
/>
|
||||
</TabWrapper>
|
||||
</ConfirmModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApplicationScopesAssignmentModal;
|
|
@ -0,0 +1,40 @@
|
|||
import { ApplicationUserConsentScopeType } from '@logto/schemas';
|
||||
|
||||
import { type PermissionTabType } from './type';
|
||||
|
||||
export const allLevelPermissionTabs: PermissionTabType = Object.freeze({
|
||||
[ApplicationUserConsentScopeType.UserScopes]: {
|
||||
title: 'application_details.permissions.user_profile',
|
||||
key: ApplicationUserConsentScopeType.UserScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.ResourceScopes]: {
|
||||
title: 'application_details.permissions.api_permissions',
|
||||
key: ApplicationUserConsentScopeType.ResourceScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.OrganizationScopes]: {
|
||||
title: 'application_details.permissions.organization',
|
||||
key: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
},
|
||||
});
|
||||
|
||||
export const userLevelPermissionsTabs: PermissionTabType = Object.freeze({
|
||||
[ApplicationUserConsentScopeType.UserScopes]: {
|
||||
title: 'application_details.permissions.user_profile',
|
||||
key: ApplicationUserConsentScopeType.UserScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.ResourceScopes]: {
|
||||
title: 'application_details.permissions.api_permissions',
|
||||
key: ApplicationUserConsentScopeType.ResourceScopes,
|
||||
},
|
||||
});
|
||||
|
||||
export const organizationLevelPermissionsTab: PermissionTabType = Object.freeze({
|
||||
[ApplicationUserConsentScopeType.OrganizationScopes]: {
|
||||
title: 'application_details.permissions.organization',
|
||||
key: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
},
|
||||
[ApplicationUserConsentScopeType.OrganizationResourceScopes]: {
|
||||
title: 'application_details.permissions.api_permissions',
|
||||
key: ApplicationUserConsentScopeType.OrganizationResourceScopes,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,160 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { ApplicationUserConsentScopeType } from '@logto/schemas';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ConfirmModal from '@/ds-components/ConfirmModal';
|
||||
import DataTransferBox from '@/ds-components/DataTransferBox';
|
||||
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
||||
import TabWrapper from '@/ds-components/TabWrapper';
|
||||
|
||||
import {
|
||||
allLevelPermissionTabs,
|
||||
organizationLevelPermissionsTab,
|
||||
userLevelPermissionsTabs,
|
||||
} from './constants';
|
||||
import { ScopeLevel } from './type';
|
||||
import useApplicationScopesAssignment from './use-application-scopes-assignment';
|
||||
|
||||
type Props = {
|
||||
readonly isOpen: boolean;
|
||||
readonly onClose: () => void;
|
||||
readonly applicationId: string;
|
||||
readonly scopeLevel: ScopeLevel;
|
||||
};
|
||||
|
||||
function ApplicationScopesAssignmentModal({ isOpen, onClose, applicationId, scopeLevel }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { activeTab, setActiveTab, scopesAssignment, clearSelectedData, onSubmit, isLoading } =
|
||||
useApplicationScopesAssignment(applicationId, scopeLevel);
|
||||
|
||||
const onCloseHandler = useCallback(() => {
|
||||
onClose();
|
||||
clearSelectedData();
|
||||
setActiveTab(
|
||||
scopeLevel === ScopeLevel.Organization
|
||||
? ApplicationUserConsentScopeType.OrganizationResourceScopes
|
||||
: ApplicationUserConsentScopeType.UserScopes
|
||||
);
|
||||
}, [clearSelectedData, onClose, scopeLevel, setActiveTab]);
|
||||
|
||||
const onSubmitHandler = useCallback(async () => {
|
||||
await onSubmit();
|
||||
onCloseHandler();
|
||||
}, [onCloseHandler, onSubmit]);
|
||||
|
||||
// If any of the tabs has selected scopes, the modal is dirty
|
||||
const isDirty = useMemo(
|
||||
() => Object.values(scopesAssignment).some(({ selectedData }) => selectedData.length > 0),
|
||||
[scopesAssignment]
|
||||
);
|
||||
|
||||
const tabs = useMemo(() => {
|
||||
const getPermissionTabs = () => {
|
||||
if (scopeLevel === ScopeLevel.All) {
|
||||
return allLevelPermissionTabs;
|
||||
}
|
||||
|
||||
return scopeLevel === ScopeLevel.User
|
||||
? userLevelPermissionsTabs
|
||||
: organizationLevelPermissionsTab;
|
||||
};
|
||||
|
||||
return Object.values(getPermissionTabs()).map(({ title, key }) => {
|
||||
const selectedDataCount = scopesAssignment[key].selectedData.length;
|
||||
|
||||
return (
|
||||
<TabNavItem
|
||||
key={key}
|
||||
isActive={key === activeTab}
|
||||
onClick={() => {
|
||||
setActiveTab(key);
|
||||
}}
|
||||
>
|
||||
{`${String(t(title))}${selectedDataCount ? ` (${selectedDataCount})` : ''}`}
|
||||
</TabNavItem>
|
||||
);
|
||||
});
|
||||
}, [activeTab, scopeLevel, scopesAssignment, setActiveTab, t]);
|
||||
|
||||
const modalText = useMemo<{
|
||||
title: AdminConsoleKey;
|
||||
subtitle: AdminConsoleKey;
|
||||
saveButton: AdminConsoleKey;
|
||||
}>(() => {
|
||||
if (scopeLevel === ScopeLevel.All) {
|
||||
return {
|
||||
title: 'application_details.permissions.table_name',
|
||||
subtitle: 'application_details.permissions.permissions_assignment_description',
|
||||
saveButton: 'general.save',
|
||||
};
|
||||
}
|
||||
|
||||
const scopeLevelPhrase = scopeLevel === ScopeLevel.User ? 'user' : 'organization';
|
||||
|
||||
return {
|
||||
title: `application_details.permissions.grant_${scopeLevelPhrase}_level_permissions`,
|
||||
subtitle: `application_details.permissions.${scopeLevelPhrase}_description`,
|
||||
saveButton: 'general.save',
|
||||
};
|
||||
}, [scopeLevel]);
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
isLoading={isLoading}
|
||||
title={modalText.title}
|
||||
subtitle={modalText.subtitle}
|
||||
isConfirmButtonDisabled={!isDirty}
|
||||
confirmButtonType="primary"
|
||||
confirmButtonText={modalText.saveButton}
|
||||
isCancelButtonVisible={false}
|
||||
size="large"
|
||||
onCancel={onCloseHandler}
|
||||
onConfirm={onSubmitHandler}
|
||||
>
|
||||
<TabNav>{tabs}</TabNav>
|
||||
{(scopeLevel === ScopeLevel.All || scopeLevel === ScopeLevel.User) && (
|
||||
<>
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.UserScopes}
|
||||
isActive={ApplicationUserConsentScopeType.UserScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox {...scopesAssignment[ApplicationUserConsentScopeType.UserScopes]} />
|
||||
</TabWrapper>
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.ResourceScopes}
|
||||
isActive={ApplicationUserConsentScopeType.ResourceScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox
|
||||
{...scopesAssignment[ApplicationUserConsentScopeType.ResourceScopes]}
|
||||
/>
|
||||
</TabWrapper>
|
||||
</>
|
||||
)}
|
||||
{(scopeLevel === ScopeLevel.All || scopeLevel === ScopeLevel.Organization) && (
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.OrganizationScopes}
|
||||
isActive={ApplicationUserConsentScopeType.OrganizationScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox
|
||||
{...scopesAssignment[ApplicationUserConsentScopeType.OrganizationScopes]}
|
||||
/>
|
||||
</TabWrapper>
|
||||
)}
|
||||
{scopeLevel === ScopeLevel.Organization && (
|
||||
<TabWrapper
|
||||
key={ApplicationUserConsentScopeType.OrganizationResourceScopes}
|
||||
isActive={ApplicationUserConsentScopeType.OrganizationResourceScopes === activeTab}
|
||||
>
|
||||
<DataTransferBox
|
||||
{...scopesAssignment[ApplicationUserConsentScopeType.OrganizationResourceScopes]}
|
||||
/>
|
||||
</TabWrapper>
|
||||
)}
|
||||
</ConfirmModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApplicationScopesAssignmentModal;
|
|
@ -1,4 +1,6 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import {
|
||||
type JsonObject,
|
||||
type ApplicationUserConsentScopesResponse,
|
||||
type ApplicationUserConsentScopeType,
|
||||
} from '@logto/schemas';
|
||||
|
@ -24,6 +26,25 @@ type CamelCase<T> = T extends `${infer A}-${infer B}` ? `${A}${Capitalize<CamelC
|
|||
export type ScopeAssignmentHook<
|
||||
T extends ApplicationUserConsentScopeType,
|
||||
V extends DataEntry = DataEntry,
|
||||
U extends JsonObject = JsonObject,
|
||||
> = (
|
||||
assignedScopes?: ApplicationUserConsentScopesResponse[CamelCase<T>]
|
||||
assignedScopes?: ApplicationUserConsentScopesResponse[CamelCase<T>],
|
||||
options?: U
|
||||
) => ScopeAssignmentHookReturnType<T, V>;
|
||||
|
||||
export enum ScopeLevel {
|
||||
User = 'user',
|
||||
Organization = 'organization',
|
||||
/**
|
||||
* Only used when the new organization resource scope feature is not ready.
|
||||
* Todo @xiaoyijun remove this when the new organization resource scope feature is ready.
|
||||
*/
|
||||
All = 'all',
|
||||
}
|
||||
|
||||
export type PermissionTabType = Partial<{
|
||||
[Key in ApplicationUserConsentScopeType]: {
|
||||
title: AdminConsoleKey;
|
||||
key: Key;
|
||||
};
|
||||
}>;
|
|
@ -8,13 +8,16 @@ import useSWR from 'swr';
|
|||
|
||||
import useApi, { type RequestError } from '@/hooks/use-api';
|
||||
|
||||
import { ScopeLevel } from './type';
|
||||
import useOrganizationScopesAssignment from './use-organization-scopes-assignment';
|
||||
import useResourceScopesAssignment from './use-resource-scopes-assignment';
|
||||
import useUserScopesAssignment from './use-user-scopes-assignment';
|
||||
|
||||
const useApplicationScopesAssignment = (applicationId: string) => {
|
||||
const useApplicationScopesAssignment = (applicationId: string, scopeLevel: ScopeLevel) => {
|
||||
const [activeTab, setActiveTab] = useState<ApplicationUserConsentScopeType>(
|
||||
ApplicationUserConsentScopeType.UserScopes
|
||||
scopeLevel === ScopeLevel.Organization
|
||||
? ApplicationUserConsentScopeType.OrganizationScopes
|
||||
: ApplicationUserConsentScopeType.UserScopes
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
|
@ -27,18 +30,31 @@ const useApplicationScopesAssignment = (applicationId: string) => {
|
|||
const userScopesAssignment = useUserScopesAssignment(data?.userScopes);
|
||||
const organizationScopesAssignment = useOrganizationScopesAssignment(data?.organizationScopes);
|
||||
const resourceScopesAssignment = useResourceScopesAssignment(data?.resourceScopes);
|
||||
const organizationResourceScopesAssignment = useResourceScopesAssignment(
|
||||
data?.organizationResourceScopes,
|
||||
{ isForOrganization: true }
|
||||
);
|
||||
|
||||
const clearSelectedData = useCallback(() => {
|
||||
userScopesAssignment.setSelectedData([]);
|
||||
organizationScopesAssignment.setSelectedData([]);
|
||||
resourceScopesAssignment.setSelectedData([]);
|
||||
}, [organizationScopesAssignment, resourceScopesAssignment, userScopesAssignment]);
|
||||
organizationScopesAssignment.setSelectedData([]);
|
||||
organizationResourceScopesAssignment.setSelectedData([]);
|
||||
}, [
|
||||
organizationResourceScopesAssignment,
|
||||
organizationScopesAssignment,
|
||||
resourceScopesAssignment,
|
||||
userScopesAssignment,
|
||||
]);
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const newUserScopes = userScopesAssignment.selectedData.map(({ id }) => id);
|
||||
const newOrganizationScopes = organizationScopesAssignment.selectedData.map(({ id }) => id);
|
||||
const newResourceScopes = resourceScopesAssignment.selectedData.map(({ id }) => id);
|
||||
const newOrganizationResourceScopes = organizationResourceScopesAssignment.selectedData.map(
|
||||
({ id }) => id
|
||||
);
|
||||
|
||||
await api
|
||||
.post(`api/applications/${applicationId}/user-consent-scopes`, {
|
||||
|
@ -50,6 +66,11 @@ const useApplicationScopesAssignment = (applicationId: string) => {
|
|||
}
|
||||
),
|
||||
...conditional(newResourceScopes.length > 0 && { resourceScopes: newResourceScopes }),
|
||||
...conditional(
|
||||
newOrganizationResourceScopes.length > 0 && {
|
||||
organizationResourceScopes: newOrganizationResourceScopes,
|
||||
}
|
||||
),
|
||||
},
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -61,6 +82,7 @@ const useApplicationScopesAssignment = (applicationId: string) => {
|
|||
api,
|
||||
applicationId,
|
||||
mutate,
|
||||
organizationResourceScopesAssignment.selectedData,
|
||||
organizationScopesAssignment.selectedData,
|
||||
resourceScopesAssignment.selectedData,
|
||||
userScopesAssignment.selectedData,
|
||||
|
@ -70,12 +92,17 @@ const useApplicationScopesAssignment = (applicationId: string) => {
|
|||
const scopesAssignment = useMemo(
|
||||
() => ({
|
||||
[ApplicationUserConsentScopeType.UserScopes]: userScopesAssignment,
|
||||
[ApplicationUserConsentScopeType.OrganizationScopes]: organizationScopesAssignment,
|
||||
[ApplicationUserConsentScopeType.ResourceScopes]: resourceScopesAssignment,
|
||||
// TODO @xiaoyijun: Replace with correct scopes
|
||||
[ApplicationUserConsentScopeType.OrganizationResourceScopes]: resourceScopesAssignment,
|
||||
[ApplicationUserConsentScopeType.OrganizationScopes]: organizationScopesAssignment,
|
||||
[ApplicationUserConsentScopeType.OrganizationResourceScopes]:
|
||||
organizationResourceScopesAssignment,
|
||||
}),
|
||||
[organizationScopesAssignment, resourceScopesAssignment, userScopesAssignment]
|
||||
[
|
||||
userScopesAssignment,
|
||||
resourceScopesAssignment,
|
||||
organizationScopesAssignment,
|
||||
organizationResourceScopesAssignment,
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
|
@ -8,21 +8,30 @@ import { useState, useMemo } from 'react';
|
|||
import useSWR from 'swr';
|
||||
|
||||
import { type RequestError } from '@/hooks/use-api';
|
||||
import { buildUrl } from '@/utils/url';
|
||||
|
||||
import { type ScopeAssignmentHook } from './type';
|
||||
|
||||
type HookType = ScopeAssignmentHook<
|
||||
ApplicationUserConsentScopeType.ResourceScopes,
|
||||
ApplicationUserConsentScopesResponse['resourceScopes'][number]['scopes'][number]
|
||||
| ApplicationUserConsentScopeType.ResourceScopes
|
||||
| ApplicationUserConsentScopeType.OrganizationResourceScopes,
|
||||
ApplicationUserConsentScopesResponse['resourceScopes'][number]['scopes'][number],
|
||||
{
|
||||
/** Whether the assignment is for an organization */
|
||||
isForOrganization?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
type SelectedDataType = ReturnType<HookType>['selectedData'][number];
|
||||
|
||||
const useResourceScopesAssignment: HookType = (assignedResourceScopes) => {
|
||||
const useResourceScopesAssignment: HookType = (assignedResourceScopes, options) => {
|
||||
const [selectedData, setSelectedData] = useState<SelectedDataType[]>([]);
|
||||
const { isForOrganization } = options ?? {};
|
||||
|
||||
const { data: allResources } = useSWR<ResourceResponse[], RequestError>(
|
||||
'api/resources?includeScopes=true'
|
||||
buildUrl('api/resources', {
|
||||
includeScopes: String(true),
|
||||
})
|
||||
);
|
||||
|
||||
const availableDataGroups = useMemo(() => {
|
||||
|
@ -58,11 +67,17 @@ const useResourceScopesAssignment: HookType = (assignedResourceScopes) => {
|
|||
}, [allResources, assignedResourceScopes]);
|
||||
|
||||
return {
|
||||
scopeType: ApplicationUserConsentScopeType.ResourceScopes,
|
||||
scopeType: isForOrganization
|
||||
? ApplicationUserConsentScopeType.OrganizationResourceScopes
|
||||
: ApplicationUserConsentScopeType.ResourceScopes,
|
||||
selectedData,
|
||||
setSelectedData,
|
||||
availableDataGroups,
|
||||
title: 'application_details.permissions.api_resource_permissions_assignment_form_title',
|
||||
title: `application_details.permissions.${
|
||||
isForOrganization
|
||||
? 'add_permissions_for_organization'
|
||||
: 'api_resource_permissions_assignment_form_title'
|
||||
}`,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
// Override the Table component styles
|
||||
.permissionsModal [class*='tableContainer'] [class*='bodyTable'] tr.sectionTitleRow {
|
||||
height: _.unit(9);
|
||||
|
||||
td {
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text-secondary);
|
||||
background-color: var(--color-layer-light);
|
||||
padding-top: _.unit(2);
|
||||
padding-bottom: _.unit(2);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
gap: _.unit(1);
|
||||
align-items: center;
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import {
|
||||
ApplicationUserConsentScopeType,
|
||||
type ApplicationUserConsentScopesResponse,
|
||||
} from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import ActionsButton from '@/components/ActionsButton';
|
||||
import Breakable from '@/components/Breakable';
|
||||
import FormCard, { type Props as FormCardProps } from '@/components/FormCard';
|
||||
import TemplateTable from '@/components/TemplateTable';
|
||||
import { logtoThirdPartyAppPermissionsLink } from '@/consts';
|
||||
import Tag from '@/ds-components/Tag';
|
||||
import { type RequestError } from '@/hooks/use-api';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
|
||||
import ApplicationScopesAssignmentModal from './ApplicationScopesAssignmentModal';
|
||||
import { ScopeLevel } from './ApplicationScopesAssignmentModal/type';
|
||||
import ApplicationScopesManagementModal, {
|
||||
type EditableScopeData,
|
||||
} from './ApplicationScopesManagementModal';
|
||||
import * as styles from './index.module.scss';
|
||||
import useScopesTable from './use-scopes-table';
|
||||
|
||||
type Props = {
|
||||
readonly applicationId: string;
|
||||
readonly scopeLevel: ScopeLevel;
|
||||
};
|
||||
|
||||
function PermissionsCard({ applicationId, scopeLevel }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
|
||||
const { data, error, mutate, isLoading } = useSWR<
|
||||
ApplicationUserConsentScopesResponse,
|
||||
RequestError
|
||||
>(`api/applications/${applicationId}/user-consent-scopes`);
|
||||
|
||||
const { parseRowGroup, deleteScope, editScope } = useScopesTable();
|
||||
|
||||
const [editScopeModalData, setEditScopeModalData] = useState<EditableScopeData>();
|
||||
const [isAssignScopesModalOpen, setIsAssignScopesModalOpen] = useState(false);
|
||||
|
||||
const rowGroups = useMemo(() => {
|
||||
const { userLevelRowGroups, organizationLevelGroups } = parseRowGroup(data);
|
||||
|
||||
if (scopeLevel === ScopeLevel.All) {
|
||||
return [...userLevelRowGroups, ...organizationLevelGroups];
|
||||
}
|
||||
|
||||
return scopeLevel === ScopeLevel.User ? userLevelRowGroups : organizationLevelGroups;
|
||||
}, [data, parseRowGroup, scopeLevel]);
|
||||
|
||||
const displayTextProps = useMemo<{
|
||||
formCard: Omit<FormCardProps, 'children'>;
|
||||
tableName: AdminConsoleKey;
|
||||
}>(() => {
|
||||
if (scopeLevel === ScopeLevel.All) {
|
||||
return {
|
||||
formCard: {
|
||||
title: 'application_details.permissions.name',
|
||||
description: 'application_details.permissions.description',
|
||||
learnMoreLink: {
|
||||
href: getDocumentationUrl(logtoThirdPartyAppPermissionsLink),
|
||||
targetBlank: 'noopener',
|
||||
},
|
||||
},
|
||||
tableName: 'application_details.permissions.table_name',
|
||||
};
|
||||
}
|
||||
|
||||
const scopeLevelPhrase = scopeLevel === ScopeLevel.User ? 'user' : 'organization';
|
||||
|
||||
return {
|
||||
formCard: {
|
||||
title: `application_details.permissions.${scopeLevelPhrase}_title`,
|
||||
description: `application_details.permissions.${scopeLevelPhrase}_description`,
|
||||
learnMoreLink: conditional(
|
||||
scopeLevel === ScopeLevel.User && {
|
||||
href: getDocumentationUrl(logtoThirdPartyAppPermissionsLink),
|
||||
targetBlank: 'noopener',
|
||||
}
|
||||
),
|
||||
},
|
||||
tableName: `application_details.permissions.grant_${scopeLevelPhrase}_level_permissions`,
|
||||
};
|
||||
}, [getDocumentationUrl, scopeLevel]);
|
||||
|
||||
return (
|
||||
<FormCard {...displayTextProps.formCard}>
|
||||
<TemplateTable
|
||||
className={styles.permissionsModal}
|
||||
name={displayTextProps.tableName}
|
||||
rowIndexKey="id"
|
||||
errorMessage={error?.body?.message ?? error?.message}
|
||||
isLoading={isLoading}
|
||||
rowGroups={rowGroups}
|
||||
columns={[
|
||||
{
|
||||
title: t('application_details.permissions.field_name'),
|
||||
dataIndex: 'name',
|
||||
colSpan: 5,
|
||||
render: ({ name }) => (
|
||||
<Tag variant="cell">
|
||||
<Breakable>{name}</Breakable>
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: `${t('general.description')} (${t(
|
||||
'application_details.permissions.field_description'
|
||||
)})`,
|
||||
dataIndex: 'description',
|
||||
colSpan: 5,
|
||||
render: ({ description }) => <Breakable>{description ?? '-'}</Breakable>,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
dataIndex: 'delete',
|
||||
render: (data) => (
|
||||
<ActionsButton
|
||||
fieldName="application_details.permissions.name"
|
||||
deleteConfirmation="application_details.permissions.permission_delete_confirm"
|
||||
textOverrides={{
|
||||
delete: 'application_details.permissions.delete_text',
|
||||
deleteConfirmation: 'general.remove',
|
||||
}}
|
||||
onEdit={
|
||||
// UserScopes is not editable
|
||||
data.type === ApplicationUserConsentScopeType.UserScopes
|
||||
? undefined
|
||||
: () => {
|
||||
setEditScopeModalData(data);
|
||||
}
|
||||
}
|
||||
onDelete={async () => {
|
||||
await deleteScope(data, applicationId);
|
||||
void mutate();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
onAdd={() => {
|
||||
setIsAssignScopesModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
{/* Render the permissions assignment modal only if the data is fetched properly */}
|
||||
{data && (
|
||||
<ApplicationScopesAssignmentModal
|
||||
isOpen={isAssignScopesModalOpen}
|
||||
applicationId={applicationId}
|
||||
scopeLevel={scopeLevel}
|
||||
onClose={() => {
|
||||
setIsAssignScopesModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{data && (
|
||||
<ApplicationScopesManagementModal
|
||||
scope={editScopeModalData}
|
||||
onClose={() => {
|
||||
setEditScopeModalData(undefined);
|
||||
}}
|
||||
onSubmit={async (scope) => {
|
||||
await editScope(scope);
|
||||
void mutate();
|
||||
setEditScopeModalData(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</FormCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default PermissionsCard;
|
|
@ -4,10 +4,12 @@ import {
|
|||
type ApplicationUserConsentScopesResponse,
|
||||
ApplicationUserConsentScopeType,
|
||||
} from '@logto/schemas';
|
||||
import { condArray } from '@silverhand/essentials';
|
||||
import { useCallback, type ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Tip from '@/assets/icons/tip.svg';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import IconButton from '@/ds-components/IconButton';
|
||||
import { ToggleTip } from '@/ds-components/Tip';
|
||||
import useApi from '@/hooks/use-api';
|
||||
|
@ -26,7 +28,9 @@ type OrganizationScopeTableRowDataType = {
|
|||
} & ApplicationUserConsentScopesResponse['organizationScopes'][number];
|
||||
|
||||
type ResourceScopeTableRowDataType = {
|
||||
type: ApplicationUserConsentScopeType.ResourceScopes;
|
||||
type:
|
||||
| ApplicationUserConsentScopeType.ResourceScopes
|
||||
| ApplicationUserConsentScopeType.OrganizationResourceScopes;
|
||||
// Resource ID is required for resource scope patch request
|
||||
resourceId: string;
|
||||
resourceName: string;
|
||||
|
@ -54,12 +58,20 @@ const useScopesTable = () => {
|
|||
const api = useApi();
|
||||
|
||||
const parseRowGroup = useCallback(
|
||||
(data?: ApplicationUserConsentScopesResponse): ScopesTableRowGroupType[] => {
|
||||
(
|
||||
data?: ApplicationUserConsentScopesResponse
|
||||
): {
|
||||
userLevelRowGroups: ScopesTableRowGroupType[];
|
||||
organizationLevelGroups: ScopesTableRowGroupType[];
|
||||
} => {
|
||||
if (!data) {
|
||||
return [];
|
||||
return {
|
||||
userLevelRowGroups: [],
|
||||
organizationLevelGroups: [],
|
||||
};
|
||||
}
|
||||
|
||||
const { organizationScopes, userScopes, resourceScopes } = data;
|
||||
const { userScopes, resourceScopes, organizationScopes, organizationResourceScopes } = data;
|
||||
|
||||
const userScopesGroup: ScopesTableRowGroupType = {
|
||||
key: ApplicationUserConsentScopeType.UserScopes,
|
||||
|
@ -85,16 +97,6 @@ const useScopesTable = () => {
|
|||
})),
|
||||
};
|
||||
|
||||
const organizationScopesGroup: ScopesTableRowGroupType = {
|
||||
key: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
label: t('application_details.permissions.organization_permissions'),
|
||||
labelRowClassName: styles.sectionTitleRow,
|
||||
data: organizationScopes.map((scope) => ({
|
||||
type: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
...scope,
|
||||
})),
|
||||
};
|
||||
|
||||
const resourceScopesGroups = resourceScopes.map<ScopesTableRowGroupType>(
|
||||
({ resource, scopes }) => ({
|
||||
key: resource.indicator,
|
||||
|
@ -109,13 +111,48 @@ const useScopesTable = () => {
|
|||
})
|
||||
);
|
||||
|
||||
return [
|
||||
// Hide the user scopes group if there is no user scopes
|
||||
...(userScopesGroup.data.length > 0 ? [userScopesGroup] : []),
|
||||
...resourceScopesGroups,
|
||||
// Hide the organization scopes group if there is no organization scopes
|
||||
...(organizationScopesGroup.data.length > 0 ? [organizationScopesGroup] : []),
|
||||
];
|
||||
const organizationScopesGroup: ScopesTableRowGroupType = {
|
||||
key: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
label: t('application_details.permissions.organization_permissions'),
|
||||
labelRowClassName: styles.sectionTitleRow,
|
||||
data: organizationScopes.map((scope) => ({
|
||||
type: ApplicationUserConsentScopeType.OrganizationScopes,
|
||||
...scope,
|
||||
})),
|
||||
};
|
||||
|
||||
const organizationResourceScopesGroup =
|
||||
organizationResourceScopes.map<ScopesTableRowGroupType>(({ resource, scopes }) => ({
|
||||
key: resource.indicator,
|
||||
label: resource.name,
|
||||
labelRowClassName: styles.sectionTitleRow,
|
||||
data: scopes.map((scope) => ({
|
||||
type: ApplicationUserConsentScopeType.OrganizationResourceScopes,
|
||||
...scope,
|
||||
resourceId: resource.id,
|
||||
resourceName: resource.name,
|
||||
})),
|
||||
}));
|
||||
|
||||
return {
|
||||
userLevelRowGroups: [
|
||||
// Hide the user scopes group if there is no user scopes
|
||||
...(userScopesGroup.data.length > 0 ? [userScopesGroup] : []),
|
||||
...resourceScopesGroups,
|
||||
],
|
||||
organizationLevelGroups: [
|
||||
// Hide the organization scopes group if there is no organization scopes
|
||||
...(organizationScopesGroup.data.length > 0 ? [organizationScopesGroup] : []),
|
||||
...condArray(
|
||||
/**
|
||||
* Hide the organization resource scopes group if the organization resource scopes feature is not ready
|
||||
*/
|
||||
isDevFeaturesEnabled &&
|
||||
organizationResourceScopesGroup.length > 0 &&
|
||||
organizationResourceScopesGroup
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
[experienceT, t]
|
||||
);
|
|
@ -1,21 +1,8 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
|
||||
// Override the Table component styles
|
||||
.permissionsModal [class*='tableContainer'] [class*='bodyTable'] tr.sectionTitleRow {
|
||||
height: _.unit(9);
|
||||
|
||||
td {
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text-secondary);
|
||||
background-color: var(--color-layer-light);
|
||||
padding-top: _.unit(2);
|
||||
padding-bottom: _.unit(2);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
.container {
|
||||
display: flex;
|
||||
gap: _.unit(1);
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: _.unit(4);
|
||||
padding-bottom: _.unit(6);
|
||||
}
|
||||
|
|
|
@ -1,140 +1,26 @@
|
|||
import {
|
||||
type Application,
|
||||
type ApplicationUserConsentScopesResponse,
|
||||
ApplicationUserConsentScopeType,
|
||||
} from '@logto/schemas';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
import { type Application } from '@logto/schemas';
|
||||
|
||||
import ActionsButton from '@/components/ActionsButton';
|
||||
import Breakable from '@/components/Breakable';
|
||||
import FormCard from '@/components/FormCard';
|
||||
import TemplateTable from '@/components/TemplateTable';
|
||||
import { logtoThirdPartyAppPermissionsLink } from '@/consts';
|
||||
import Tag from '@/ds-components/Tag';
|
||||
import { type RequestError } from '@/hooks/use-api';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
|
||||
import ApplicationScopesAssignmentModal from './ApplicationScopesAssignmentModal';
|
||||
import ApplicationScopesManagementModal, {
|
||||
type EditableScopeData,
|
||||
} from './ApplicationScopesManagementModal';
|
||||
import PermissionsCard from './PermissionsCard';
|
||||
import { ScopeLevel } from './PermissionsCard/ApplicationScopesAssignmentModal/type';
|
||||
import * as styles from './index.module.scss';
|
||||
import useScopesTable from './use-scopes-table';
|
||||
|
||||
type Props = {
|
||||
readonly application: Application;
|
||||
};
|
||||
|
||||
function Permissions({ application }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
|
||||
const [isAssignScopesModalOpen, setIsAssignScopesModalOpen] = useState(false);
|
||||
const [editScopeModalData, setEditScopeModalData] = useState<EditableScopeData>();
|
||||
|
||||
const { parseRowGroup, deleteScope, editScope } = useScopesTable();
|
||||
|
||||
const { data, error, mutate, isLoading } = useSWR<
|
||||
ApplicationUserConsentScopesResponse,
|
||||
RequestError
|
||||
>(`api/applications/${application.id}/user-consent-scopes`);
|
||||
|
||||
const rowGroups = useMemo(() => parseRowGroup(data), [data, parseRowGroup]);
|
||||
const displayScopeLevels = isDevFeaturesEnabled
|
||||
? [ScopeLevel.User, ScopeLevel.Organization]
|
||||
: [ScopeLevel.All];
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormCard
|
||||
title="application_details.permissions.name"
|
||||
description="application_details.permissions.description"
|
||||
learnMoreLink={{
|
||||
href: getDocumentationUrl(logtoThirdPartyAppPermissionsLink),
|
||||
targetBlank: 'noopener',
|
||||
}}
|
||||
>
|
||||
<TemplateTable
|
||||
className={styles.permissionsModal}
|
||||
name="application_details.permissions.table_name"
|
||||
rowIndexKey="id"
|
||||
errorMessage={error?.body?.message ?? error?.message}
|
||||
isLoading={isLoading}
|
||||
rowGroups={rowGroups}
|
||||
columns={[
|
||||
{
|
||||
title: t('application_details.permissions.field_name'),
|
||||
dataIndex: 'name',
|
||||
colSpan: 5,
|
||||
render: ({ name }) => (
|
||||
<Tag variant="cell">
|
||||
<Breakable>{name}</Breakable>
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: `${t('general.description')} (${t(
|
||||
'application_details.permissions.field_description'
|
||||
)})`,
|
||||
dataIndex: 'description',
|
||||
colSpan: 5,
|
||||
render: ({ description }) => <Breakable>{description ?? '-'}</Breakable>,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
dataIndex: 'delete',
|
||||
render: (data) => (
|
||||
<ActionsButton
|
||||
fieldName="application_details.permissions.name"
|
||||
deleteConfirmation="application_details.permissions.permission_delete_confirm"
|
||||
textOverrides={{
|
||||
delete: 'application_details.permissions.delete_text',
|
||||
deleteConfirmation: 'general.remove',
|
||||
}}
|
||||
onEdit={
|
||||
// UserScopes is not editable
|
||||
data.type === ApplicationUserConsentScopeType.UserScopes
|
||||
? undefined
|
||||
: () => {
|
||||
setEditScopeModalData(data);
|
||||
}
|
||||
}
|
||||
onDelete={async () => {
|
||||
await deleteScope(data, application.id);
|
||||
void mutate();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
onAdd={() => {
|
||||
setIsAssignScopesModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
</FormCard>
|
||||
{/* Render the permissions assignment modal only if the data is fetched properly */}
|
||||
{data && (
|
||||
<ApplicationScopesAssignmentModal
|
||||
applicationId={application.id}
|
||||
isOpen={isAssignScopesModalOpen}
|
||||
onClose={() => {
|
||||
setIsAssignScopesModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{data && (
|
||||
<ApplicationScopesManagementModal
|
||||
scope={editScopeModalData}
|
||||
onClose={() => {
|
||||
setEditScopeModalData(undefined);
|
||||
}}
|
||||
onSubmit={async (scope) => {
|
||||
await editScope(scope);
|
||||
void mutate();
|
||||
setEditScopeModalData(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<div className={styles.container}>
|
||||
{displayScopeLevels.map((scopeLevel) => (
|
||||
<PermissionsCard key={scopeLevel} applicationId={application.id} scopeLevel={scopeLevel} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Rolle',
|
||||
|
|
|
@ -111,8 +111,8 @@ const application_details = {
|
|||
permissions_assignment_description:
|
||||
'Select the permissions the third-party application requests for user authorization to access specific data types.',
|
||||
user_profile: 'User data',
|
||||
api_resource: 'API resource',
|
||||
organization: 'Organization',
|
||||
api_permissions: 'API permissions',
|
||||
organization: 'Organization permissions',
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
organization_permissions_assignment_form_title: 'Add the organization permissions',
|
||||
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
|
||||
|
@ -120,6 +120,16 @@ const application_details = {
|
|||
'You can modify the description of the personal user data permissions via "Sign-in Experience > Content > Manage Language"',
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
user_title: 'User',
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
organization_title: 'Organization',
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Role',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Rol',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Rôle',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Ruolo',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: '役割',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: '역할',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Role',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Função',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Nome da função',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Роль',
|
||||
|
|
|
@ -156,9 +156,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -171,6 +171,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: 'Rol',
|
||||
|
|
|
@ -154,9 +154,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -169,6 +169,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: '角色',
|
||||
|
|
|
@ -154,9 +154,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -169,6 +169,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: '角色',
|
||||
|
|
|
@ -155,9 +155,9 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
user_profile: 'User data',
|
||||
/** UNTRANSLATED */
|
||||
api_resource: 'API resource',
|
||||
api_permissions: 'API permissions',
|
||||
/** UNTRANSLATED */
|
||||
organization: 'Organization',
|
||||
organization: 'Organization permissions',
|
||||
/** UNTRANSLATED */
|
||||
user_permissions_assignment_form_title: 'Add the user profile permissions',
|
||||
/** UNTRANSLATED */
|
||||
|
@ -170,6 +170,23 @@ const application_details = {
|
|||
/** UNTRANSLATED */
|
||||
permission_description_tips:
|
||||
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
|
||||
/** UNTRANSLATED */
|
||||
user_title: 'User',
|
||||
/** UNTRANSLATED */
|
||||
user_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific user data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_user_level_permissions: 'Grant permissions of user data',
|
||||
/** UNTRANSLATED */
|
||||
organization_title: 'Organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_description:
|
||||
'Select the permissions requested by the third-party app for accessing specific organization data.',
|
||||
/** UNTRANSLATED */
|
||||
grant_organization_level_permissions: 'Grant permissions of organization data',
|
||||
/** UNTRANSLATED */
|
||||
add_permissions_for_organization:
|
||||
'Add the API resource permissions used in the "Organization template"',
|
||||
},
|
||||
roles: {
|
||||
name_column: '角色',
|
||||
|
|
Loading…
Add table
Reference in a new issue