diff --git a/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx b/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx
index 584dcf7c2..817fc725d 100644
--- a/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx
+++ b/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx
@@ -1,5 +1,5 @@
import type { ResourceResponse, Scope, ScopeResponse } from '@logto/schemas';
-import { isManagementApi, PredefinedScope } from '@logto/schemas';
+import { isManagementApi, PredefinedScope, RoleType } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import classNames from 'classnames';
import type { ChangeEvent } from 'react';
@@ -10,6 +10,7 @@ import useSWR from 'swr';
import Search from '@/assets/icons/search.svg';
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
import type { DetailedResourceResponse } from '@/components/RoleScopesTransfer/types';
+import { isProduction } from '@/consts/env';
import TextInput from '@/ds-components/TextInput';
import type { RequestError } from '@/hooks/use-api';
import * as transferLayout from '@/scss/transfer.module.scss';
@@ -20,11 +21,12 @@ import * as styles from './index.module.scss';
type Props = {
roleId?: string;
+ roleType: RoleType;
selectedScopes: ScopeResponse[];
onChange: (value: ScopeResponse[]) => void;
};
-function SourceScopesBox({ roleId, selectedScopes, onChange }: Props) {
+function SourceScopesBox({ roleId, roleType, selectedScopes, onChange }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { data: allResources, error: fetchAllResourcesError } = useSWR<
@@ -85,7 +87,10 @@ function SourceScopesBox({ roleId, selectedScopes, onChange }: Props) {
return allResources
.filter(
({ indicator, scopes }) =>
- !isManagementApi(indicator) && scopes.some(({ id }) => !excludeScopeIds.has(id))
+ /** Should show management API scopes for machine-to-machine roles */
+ ((!isProduction && roleType === RoleType.MachineToMachine) ||
+ !isManagementApi(indicator)) &&
+ scopes.some(({ id }) => !excludeScopeIds.has(id))
)
.map(({ scopes, ...resource }) => ({
...resource,
@@ -96,7 +101,7 @@ function SourceScopesBox({ roleId, selectedScopes, onChange }: Props) {
resource,
})),
}));
- }, [allResources, roleId, roleScopes]);
+ }, [allResources, roleType, roleId, roleScopes]);
const dataSource = useMemo(() => {
const lowerCasedKeyword = keyword.toLowerCase();
diff --git a/packages/console/src/components/RoleScopesTransfer/index.tsx b/packages/console/src/components/RoleScopesTransfer/index.tsx
index 14f408ad2..9368422fb 100644
--- a/packages/console/src/components/RoleScopesTransfer/index.tsx
+++ b/packages/console/src/components/RoleScopesTransfer/index.tsx
@@ -1,4 +1,4 @@
-import type { ScopeResponse } from '@logto/schemas';
+import type { ScopeResponse, RoleType } from '@logto/schemas';
import classNames from 'classnames';
import * as transferLayout from '@/scss/transfer.module.scss';
@@ -9,14 +9,20 @@ import * as styles from './index.module.scss';
type Props = {
roleId?: string;
+ roleType: RoleType;
value: ScopeResponse[];
onChange: (value: ScopeResponse[]) => void;
};
-function RoleScopesTransfer({ roleId, value, onChange }: Props) {
+function RoleScopesTransfer({ roleId, roleType, value, onChange }: Props) {
return (
diff --git a/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx b/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx
index 6f6704753..c8b3e63a9 100644
--- a/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx
+++ b/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx
@@ -1,4 +1,4 @@
-import type { ScopeResponse } from '@logto/schemas';
+import type { ScopeResponse, RoleType } from '@logto/schemas';
import { useContext, useState } from 'react';
import { toast } from 'react-hot-toast';
import { Trans, useTranslation } from 'react-i18next';
@@ -19,11 +19,12 @@ import { hasReachedQuotaLimit } from '@/utils/quota';
type Props = {
roleId: string;
+ roleType: RoleType;
totalRoleScopeCount: number;
onClose: (success?: boolean) => void;
};
-function AssignPermissionsModal({ roleId, totalRoleScopeCount, onClose }: Props) {
+function AssignPermissionsModal({ roleId, roleType, totalRoleScopeCount, onClose }: Props) {
const { currentTenantId } = useContext(TenantsContext);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
@@ -108,6 +109,7 @@ function AssignPermissionsModal({ roleId, totalRoleScopeCount, onClose }: Props)
{
setScopes(scopes);
diff --git a/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx b/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx
index 94b87242a..07423446b 100644
--- a/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx
+++ b/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx
@@ -22,7 +22,7 @@ const pageSize = defaultPageSize;
function RolePermissions() {
const {
- role: { id: roleId },
+ role: { id: roleId, type: roleType },
} = useOutletContext();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@@ -117,6 +117,7 @@ function RolePermissions() {
{isAssignPermissionsModalOpen && totalCount !== undefined && (
{
if (success) {
diff --git a/packages/console/src/pages/Roles/components/CreateRoleForm/index.module.scss b/packages/console/src/pages/Roles/components/CreateRoleForm/index.module.scss
new file mode 100644
index 000000000..2aaef3ad4
--- /dev/null
+++ b/packages/console/src/pages/Roles/components/CreateRoleForm/index.module.scss
@@ -0,0 +1,6 @@
+@use '@/scss/underscore' as _;
+
+.roleTypes {
+ display: flex;
+ gap: _.unit(6);
+}
diff --git a/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx b/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx
index 9cb8e67f6..b504e2bea 100644
--- a/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx
+++ b/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx
@@ -1,5 +1,6 @@
+import { type AdminConsoleKey } from '@logto/phrases';
import type { Role, ScopeResponse } from '@logto/schemas';
-import { internalRolePrefix } from '@logto/schemas';
+import { RoleType, internalRolePrefix } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import { useContext } from 'react';
import { Controller, useForm } from 'react-hook-form';
@@ -9,26 +10,36 @@ import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
import PlanName from '@/components/PlanName';
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
import RoleScopesTransfer from '@/components/RoleScopesTransfer';
+import { isProduction } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
import Button from '@/ds-components/Button';
+import DynamicT from '@/ds-components/DynamicT';
import FormField from '@/ds-components/FormField';
import ModalLayout from '@/ds-components/ModalLayout';
+import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
import TextInput from '@/ds-components/TextInput';
import useApi from '@/hooks/use-api';
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
import { trySubmitSafe } from '@/utils/form';
import { hasReachedQuotaLimit } from '@/utils/quota';
+import * as styles from './index.module.scss';
+
+const radioOptions: Array<{ key: AdminConsoleKey; value: RoleType }> = [
+ { key: 'roles.type_user', value: RoleType.User },
+ { key: 'roles.type_machine_to_machine', value: RoleType.MachineToMachine },
+];
+
export type Props = {
totalRoleCount: number;
onClose: (createdRole?: Role) => void;
};
-type CreateRoleFormData = Pick & {
+type CreateRoleFormData = Pick & {
scopes: ScopeResponse[];
};
-type CreateRolePayload = Pick & {
+type CreateRolePayload = Pick & {
scopeIds?: string[];
};
@@ -42,13 +53,13 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
register,
watch,
formState: { isSubmitting, errors },
- } = useForm();
+ } = useForm({ defaultValues: { type: RoleType.User } });
const api = useApi();
const roleScopes = watch('scopes', []);
const onSubmit = handleSubmit(
- trySubmitSafe(async ({ name, description, scopes }) => {
+ trySubmitSafe(async ({ name, description, type, scopes }) => {
if (isSubmitting) {
return;
}
@@ -56,6 +67,7 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
const payload: CreateRolePayload = {
name,
description,
+ type,
scopeIds: conditional(scopes.length > 0 && scopes.map(({ id }) => id)),
};
@@ -146,6 +158,28 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
error={errors.name?.message}
/>
+ {!isProduction && (
+
+ (
+ {
+ onChange(value);
+ }}
+ >
+ {radioOptions.map(({ key, value }) => (
+ } value={value} />
+ ))}
+
+ )}
+ />
+
+ )}
(
-
+
)}
/>
diff --git a/packages/phrases/src/locales/de/translation/admin-console/roles.ts b/packages/phrases/src/locales/de/translation/admin-console/roles.ts
index cd2b00030..f55129ea6 100644
--- a/packages/phrases/src/locales/de/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/de/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'Rollen beinhalten Berechtigungen, die bestimmen, was ein Benutzer tun kann. RBAC verwendet Rollen, um Benutzern Zugriff auf Ressourcen für bestimmte Aktionen zu geben.',
create: 'Rolle erstellen',
- role_name: 'Rolle',
+ role_name: 'Rollenname',
+ role_type: 'Rollenart',
+ type_user: 'Benutzerrolle',
+ type_machine_to_machine: 'Maschinen-zu-Maschinen-App-Rolle',
role_description: 'Beschreibung',
role_name_placeholder: 'Geben Sie Ihren Rollennamen ein',
role_description_placeholder: 'Geben Sie Ihre Rollenbeschreibung ein',
diff --git a/packages/phrases/src/locales/en/translation/admin-console/roles.ts b/packages/phrases/src/locales/en/translation/admin-console/roles.ts
index 0a02fcba9..d6e0cef49 100644
--- a/packages/phrases/src/locales/en/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/en/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'Roles include permissions that determine what a user can do. RBAC uses roles to give users access to resources for specific actions.',
create: 'Create Role',
- role_name: 'Role',
+ role_name: 'Role name',
+ role_type: 'Role type',
+ type_user: 'User role',
+ type_machine_to_machine: 'Machine-to-machine app role',
role_description: 'Description',
role_name_placeholder: 'Enter your role name',
role_description_placeholder: 'Enter your role description',
diff --git a/packages/phrases/src/locales/es/translation/admin-console/roles.ts b/packages/phrases/src/locales/es/translation/admin-console/roles.ts
index 02dbff0cf..42a9a33f7 100644
--- a/packages/phrases/src/locales/es/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/es/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'Los roles incluyen permisos que determinan lo que un usuario puede hacer. RBAC utiliza roles para dar acceso a recursos a los usuarios para acciones específicas.',
create: 'Crear Rol',
- role_name: 'Rol',
+ role_name: 'Nombre de rol',
+ role_type: 'Tipo de rol',
+ type_user: 'Rol de usuario',
+ type_machine_to_machine: 'Rol de aplicación de máquina a máquina',
role_description: 'Descripción',
role_name_placeholder: 'Ingrese el nombre de su rol',
role_description_placeholder: 'Ingrese la descripción de su rol',
diff --git a/packages/phrases/src/locales/fr/translation/admin-console/roles.ts b/packages/phrases/src/locales/fr/translation/admin-console/roles.ts
index 9c2edc018..c4c37983f 100644
--- a/packages/phrases/src/locales/fr/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/fr/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
"Les rôles incluent des autorisations qui déterminent ce qu'un utilisateur peut faire. RBAC utilise des rôles pour donner aux utilisateurs accès à des ressources pour des actions spécifiques.",
create: 'Créer un rôle',
- role_name: 'Rôle',
+ role_name: 'Nom du rôle',
+ role_type: 'Type de rôle',
+ type_user: 'Rôle utilisateur',
+ type_machine_to_machine: "Rôle d'application machine-à-machine",
role_description: 'Description',
role_name_placeholder: 'Entrez le nom de votre rôle',
role_description_placeholder: 'Entrez la description de votre rôle',
diff --git a/packages/phrases/src/locales/it/translation/admin-console/roles.ts b/packages/phrases/src/locales/it/translation/admin-console/roles.ts
index a78c2f2ef..983954c55 100644
--- a/packages/phrases/src/locales/it/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/it/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
"I ruoli includono le autorizzazioni che determinano ciò che un utente può fare. RBAC utilizza i ruoli per dare agli utenti l'accesso alle risorse necessarie per specifiche azioni.",
create: 'Crea Ruolo',
- role_name: 'Ruolo',
+ role_name: 'Nome ruolo',
+ role_type: 'Tipo ruolo',
+ type_user: 'Ruolo utente',
+ type_machine_to_machine: 'Ruolo app M2M',
role_description: 'Descrizione',
role_name_placeholder: 'Inserisci il nome del tuo ruolo',
role_description_placeholder: 'Inserisci la descrizione del tuo ruolo',
diff --git a/packages/phrases/src/locales/ja/translation/admin-console/roles.ts b/packages/phrases/src/locales/ja/translation/admin-console/roles.ts
index 2369aa0b2..ea367782e 100644
--- a/packages/phrases/src/locales/ja/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/ja/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'ロールには、ユーザーが実行できるアクションを決定する権限が含まれます。RBACは、特定のアクションのためにリソースにアクセスするためにユーザーに権限を付与するために、ロールを使用します。',
create: 'ロールを作成する',
- role_name: 'ロールの名前',
+ role_name: '役割名',
+ role_type: '役割タイプ',
+ type_user: 'ユーザーの役割',
+ type_machine_to_machine: 'マシン対マシンアプリの役割',
role_description: '説明',
role_name_placeholder: 'ロールの名前を入力してください',
role_description_placeholder: 'ロールの説明を入力してください',
diff --git a/packages/phrases/src/locales/ko/translation/admin-console/roles.ts b/packages/phrases/src/locales/ko/translation/admin-console/roles.ts
index 0c3ca68b1..4897a9ec5 100644
--- a/packages/phrases/src/locales/ko/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/ko/translation/admin-console/roles.ts
@@ -5,6 +5,9 @@ const roles = {
'역할은 사용자가 무엇을 할 수 있는지를 결정하는 권한을 포함해요. RBAC는 사용자에게 특정 행동에 대한 접근 권한을 부여하기 위해 역할을 사용해요.',
create: '역할 생성',
role_name: '역할 이름',
+ role_type: '역할 유형',
+ type_user: '사용자 역할',
+ type_machine_to_machine: '기계 간 앱 역할',
role_description: '설명',
role_name_placeholder: '역할 이름을 입력하세요',
role_description_placeholder: '역할 설명을 입력하세요',
diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts
index 5601bf163..312ddb1c5 100644
--- a/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'Role zawiera uprawnienia określające, co użytkownik może robić. RBAC wykorzystuje role do udostępniania użytkownikom zasobów do określonych działań.',
create: 'Utwórz rolę',
- role_name: 'Rola',
+ role_name: 'Nazwa roli',
+ role_type: 'Typ roli',
+ type_user: 'Rola użytkownika',
+ type_machine_to_machine: 'Rola aplikacji Machine-to-Machine',
role_description: 'Opis',
role_name_placeholder: 'Wprowadź nazwę swojej roli',
role_description_placeholder: 'Wprowadź opis swojej roli',
diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts
index 193365f91..eeaf5c83b 100644
--- a/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'As funções incluem permissões que determinam o que um usuário pode fazer. O RBAC usa funções para dar aos usuários acesso a recursos para ações específicas.',
create: 'Criar função',
- role_name: 'Função',
+ role_name: 'Nome da função',
+ role_type: 'Tipo de função',
+ type_user: 'Função do usuário',
+ type_machine_to_machine: 'Função do aplicativo de máquina para máquina',
role_description: 'Descrição',
role_name_placeholder: 'Insira o nome da sua função',
role_description_placeholder: 'Insira a descrição da sua função',
diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts
index c39893409..630e443f2 100644
--- a/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts
@@ -5,6 +5,9 @@ const roles = {
'Os papéis incluem permissões que determinam o que um usuário pode fazer. RBAC usa papéis para conceder acesso a recursos para ações específicas.',
create: 'Criar papel',
role_name: 'Nome do papel',
+ role_type: 'Tipo de papel',
+ type_user: 'Função de usuário',
+ type_machine_to_machine: 'Função de aplicação máquina-a-máquina',
role_description: 'Descrição',
role_name_placeholder: 'Digite o nome do papel',
role_description_placeholder: 'Digite a descrição do papel',
diff --git a/packages/phrases/src/locales/ru/translation/admin-console/roles.ts b/packages/phrases/src/locales/ru/translation/admin-console/roles.ts
index b1fc1d8c3..6b00fa3f5 100644
--- a/packages/phrases/src/locales/ru/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/ru/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'Роли включают права доступа, которые определяют, что может делать пользователь. RBAC использует роли для предоставления пользователям доступа к ресурсам для конкретных действий.',
create: 'Создать роль',
- role_name: 'Роль',
+ role_name: 'Имя роли',
+ role_type: 'Тип роли',
+ type_user: 'Роль пользователя',
+ type_machine_to_machine: 'Роль приложения между машинами',
role_description: 'Описание',
role_name_placeholder: 'Введите название роли',
role_description_placeholder: 'Введите описание роли',
diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts
index b2b8f9907..f9ab700a7 100644
--- a/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts
@@ -4,7 +4,10 @@ const roles = {
subtitle:
'Roller, bir kullanıcının ne yapabileceğini belirleyen izinleri içerir. RBAC, kullanıcılara belirli işlemler için kaynaklara erişim vermek için roller kullanır.',
create: 'Rol Oluştur',
- role_name: 'Rol Adı',
+ role_name: 'Rol adı',
+ role_type: 'Rol tipi',
+ type_user: 'Kullanıcı rolü',
+ type_machine_to_machine: 'Makine-makine uygulama rolü',
role_description: 'Açıklama',
role_name_placeholder: 'Rol adınızı girin',
role_description_placeholder: 'Rol açıklamanızı girin',
diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts
index 0352c1d78..4acd00b42 100644
--- a/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts
@@ -5,6 +5,9 @@ const roles = {
'RBAC 是一种访问控制方法,它使用角色来决定用户可以做什么事情,包括授予用户访问特定资源的权限。',
create: '创建角色',
role_name: '角色名称',
+ role_type: '角色类型',
+ type_user: '用户角色',
+ type_machine_to_machine: '机器对机器应用程序角色',
role_description: '描述',
role_name_placeholder: '输入你的角色名称',
role_description_placeholder: '输入你的角色描述',
diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts
index 50cb8f552..63438de60 100644
--- a/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts
@@ -5,6 +5,9 @@ const roles = {
'RBAC 是一種訪問控制方法,它使用角色來決定用戶可以做什麼事情,包括授予用戶訪問特定資源的權限。',
create: '創建角色',
role_name: '角色名稱',
+ role_type: '角色類型',
+ type_user: '用戶角色',
+ type_machine_to_machine: '機器到機器應用程式角色',
role_description: '描述',
role_name_placeholder: '輸入你的角色名稱',
role_description_placeholder: '輸入你的角色描述',
diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts
index 61b7f83a8..7a5607a2a 100644
--- a/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts
+++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts
@@ -5,6 +5,9 @@ const roles = {
'RBAC 是一種訪問控制方法,它使用角色來決定用戶可以做什麼事情,包括授予用戶訪問特定資源的權限。',
create: '建立角色',
role_name: '角色名稱',
+ role_type: '角色類型',
+ type_user: '使用者角色',
+ type_machine_to_machine: '機器對機器應用角色',
role_description: '描述',
role_name_placeholder: '輸入你的角色名稱',
role_description_placeholder: '輸入你的角色描述',