diff --git a/packages/core/src/__mocks__/index.ts b/packages/core/src/__mocks__/index.ts index 34e62fb4b..0361a650a 100644 --- a/packages/core/src/__mocks__/index.ts +++ b/packages/core/src/__mocks__/index.ts @@ -2,6 +2,7 @@ import { VerificationCodeType } from '@logto/connector-kit'; import type { AdminConsoleData, Application, + ApplicationsRole, Passcode, Resource, Role, @@ -120,3 +121,10 @@ export const mockUserRole: UsersRole = { userId: 'foo', roleId: 'role_id', }; + +export const mockApplicationRole: ApplicationsRole = { + tenantId: 'fake_tenant', + id: 'application_role_id', + applicationId: 'application_id', + roleId: 'role_id', +}; diff --git a/packages/core/src/routes/application-role.test.ts b/packages/core/src/routes/application-role.test.ts new file mode 100644 index 000000000..eb8c755fd --- /dev/null +++ b/packages/core/src/routes/application-role.test.ts @@ -0,0 +1,98 @@ +import { ApplicationType } from '@logto/schemas'; +import { pickDefault } from '@logto/shared/esm'; + +import { + mockAdminApplicationRole, + mockApplication, + mockAdminUserRole2, + mockApplicationRole, +} from '#src/__mocks__/index.js'; +import { mockId, mockStandardId } from '#src/test-utils/nanoid.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; +import { createRequester } from '#src/utils/test-utils.js'; + +const { jest } = import.meta; + +await mockStandardId(); + +const mockM2mApplication = { ...mockApplication, type: ApplicationType.MachineToMachine }; + +const applications = { findApplicationById: jest.fn(async () => mockM2mApplication) }; + +const roles = { + findRolesByRoleIds: jest.fn(), + findRoleById: jest.fn(), + countRoles: jest.fn(async () => ({ count: 1 })), + findRoles: jest.fn(async () => [mockAdminApplicationRole]), +}; +const { findRolesByRoleIds } = roles; + +const applicationsRoles = { + findApplicationsRolesByApplicationId: jest.fn(), + insertApplicationsRoles: jest.fn(), + deleteApplicationRole: jest.fn(), +}; +const { findApplicationsRolesByApplicationId, insertApplicationsRoles, deleteApplicationRole } = + applicationsRoles; + +const tenantContext = new MockTenant(undefined, { applicationsRoles, applications, roles }); + +const applicationRoleRoutes = await pickDefault(import('./application-role.js')); + +describe('application role routes', () => { + const applicationRoleRequester = createRequester({ + authedRoutes: applicationRoleRoutes, + tenantContext, + }); + + it('GET /applications/:applicationId/roles', async () => { + findApplicationsRolesByApplicationId.mockResolvedValueOnce([]); + const response = await applicationRoleRequester.get( + `/applications/${mockM2mApplication.id}/roles` + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual([mockAdminApplicationRole]); + }); + + it('POST /applications/:applicationId/roles', async () => { + findApplicationsRolesByApplicationId.mockResolvedValueOnce([]); + findRolesByRoleIds.mockResolvedValueOnce([]); + const response = await applicationRoleRequester + .post(`/applications/${mockM2mApplication.id}/roles`) + .send({ + roleIds: [mockAdminApplicationRole.id], + }); + expect(response.status).toEqual(201); + expect(insertApplicationsRoles).toHaveBeenCalledWith([ + { id: mockId, applicationId: mockM2mApplication.id, roleId: mockAdminApplicationRole.id }, + ]); + }); + + it('PUT /applications/:applicationId/roles', async () => { + findApplicationsRolesByApplicationId.mockResolvedValueOnce([mockApplicationRole]); + const response = await applicationRoleRequester + .put(`/applications/${mockM2mApplication.id}/roles`) + .send({ + roleIds: [mockAdminUserRole2.id], + }); + expect(response.status).toEqual(200); + expect(deleteApplicationRole).toHaveBeenCalledWith( + mockM2mApplication.id, + mockAdminApplicationRole.id + ); + expect(insertApplicationsRoles).toHaveBeenCalledWith([ + { id: mockId, applicationId: mockM2mApplication.id, roleId: mockAdminUserRole2.id }, + ]); + }); + + it('DELETE /applications/:applicationId/roles/:roleId', async () => { + const response = await applicationRoleRequester.delete( + `/applications/${mockM2mApplication.id}/roles/${mockAdminApplicationRole.id}` + ); + expect(response.status).toEqual(204); + expect(deleteApplicationRole).toHaveBeenCalledWith( + mockM2mApplication.id, + mockAdminApplicationRole.id + ); + }); +}); diff --git a/packages/core/src/routes/application-role.ts b/packages/core/src/routes/application-role.ts new file mode 100644 index 000000000..73a57e925 --- /dev/null +++ b/packages/core/src/routes/application-role.ts @@ -0,0 +1,206 @@ +import { ApplicationType, RoleType, Roles } from '@logto/schemas'; +import { generateStandardId } from '@logto/shared'; +import { tryThat } from '@silverhand/essentials'; +import { array, object, string } from 'zod'; + +import RequestError from '#src/errors/RequestError/index.js'; +import koaGuard from '#src/middleware/koa-guard.js'; +import koaPagination from '#src/middleware/koa-pagination.js'; +import koaRoleRlsErrorHandler from '#src/middleware/koa-role-rls-error-handler.js'; +import assertThat from '#src/utils/assert-that.js'; +import { parseSearchParamsForSearch } from '#src/utils/search.js'; + +import type { AuthedRouter, RouterInitArgs } from './types.js'; + +export default function applicationRoleRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { + roles: { findRoleById, countRoles, findRoles, findRolesByRoleIds }, + applications: { findApplicationById }, + applicationsRoles: { + findApplicationsRolesByApplicationId, + insertApplicationsRoles, + deleteApplicationRole, + }, + } = queries; + + router.use('/applications/:applicationId/roles(/.*)?', koaRoleRlsErrorHandler()); + + router.get( + '/applications/:applicationId/roles', + koaPagination(), + koaGuard({ + params: object({ applicationId: string() }), + response: array(Roles.guard), + status: [200, 400, 404, 422], + }), + async (ctx, next) => { + const { applicationId } = ctx.guard.params; + const { limit, offset } = ctx.pagination; + const { searchParams } = ctx.request.URL; + + const application = await findApplicationById(applicationId); + assertThat( + application.type === ApplicationType.MachineToMachine, + new RequestError({ + code: 'application.invalid_type', + status: 422, + }) + ); + + return tryThat( + async () => { + const search = parseSearchParamsForSearch(searchParams); + const applicationRoles = await findApplicationsRolesByApplicationId(applicationId); + const roleIds = applicationRoles.map(({ roleId }) => roleId); + const [{ count }, roles] = await Promise.all([ + countRoles(search, { roleIds }), + findRoles(search, limit, offset, { roleIds }), + ]); + + // Return totalCount to pagination middleware + ctx.pagination.totalCount = count; + ctx.body = roles; + + return next(); + }, + (error) => { + if (error instanceof TypeError) { + throw new RequestError( + { code: 'request.invalid_input', details: error.message }, + error + ); + } + throw error; + } + ); + } + ); + + router.post( + '/applications/:applicationId/roles', + koaGuard({ + params: object({ applicationId: string() }), + body: object({ roleIds: string().min(1).array() }), + status: [201, 404, 422], + }), + async (ctx, next) => { + const { + params: { applicationId }, + body: { roleIds }, + } = ctx.guard; + + const application = await findApplicationById(applicationId); + assertThat( + application.type === ApplicationType.MachineToMachine, + new RequestError({ + code: 'application.invalid_type', + status: 422, + }) + ); + const applicationRoles = await findApplicationsRolesByApplicationId(applicationId); + + const roles = await findRolesByRoleIds(roleIds); + + for (const role of roles) { + assertThat( + !applicationRoles.some(({ roleId: _roleId }) => _roleId === role.id), + new RequestError({ + code: 'application.role_exists', + status: 422, + roleId: role.id, + }) + ); + assertThat( + role.type === RoleType.MachineToMachine, + new RequestError({ + code: 'application.invalid_role_type', + status: 422, + }) + ); + } + + await Promise.all(roleIds.map(async (roleId) => findRoleById(roleId))); + await insertApplicationsRoles( + roleIds.map((roleId) => ({ id: generateStandardId(), applicationId, roleId })) + ); + ctx.status = 201; + + return next(); + } + ); + + router.put( + '/applications/:applicationId/roles', + koaGuard({ + params: object({ applicationId: string() }), + body: object({ roleIds: string().min(1).array() }), + status: [200, 404, 422], + }), + async (ctx, next) => { + const { + params: { applicationId }, + body: { roleIds }, + } = ctx.guard; + + const application = await findApplicationById(applicationId); + assertThat( + application.type === ApplicationType.MachineToMachine, + new RequestError({ + code: 'application.invalid_type', + status: 422, + }) + ); + const applicationRoles = await findApplicationsRolesByApplicationId(applicationId); + + // Only add the ones that doesn't exist + const roleIdsToAdd = roleIds.filter( + (roleId) => !applicationRoles.some(({ roleId: _roleId }) => _roleId === roleId) + ); + // Remove existing roles that isn't wanted by app anymore + const roleIdsToRemove = applicationRoles + .filter(({ roleId }) => !roleIds.includes(roleId)) + .map(({ roleId }) => roleId); + + await Promise.all(roleIdsToAdd.map(async (roleId) => findRoleById(roleId))); + await Promise.all( + roleIdsToRemove.map(async (roleId) => deleteApplicationRole(applicationId, roleId)) + ); + await insertApplicationsRoles( + roleIdsToAdd.map((roleId) => ({ id: generateStandardId(), applicationId, roleId })) + ); + + ctx.status = 200; + + return next(); + } + ); + + router.delete( + '/applications/:applicationId/roles/:roleId', + koaGuard({ + params: object({ applicationId: string(), roleId: string() }), + status: [204, 404, 422], + }), + async (ctx, next) => { + const { + params: { applicationId, roleId }, + } = ctx.guard; + + const application = await findApplicationById(applicationId); + assertThat( + application.type === ApplicationType.MachineToMachine, + new RequestError({ + code: 'application.invalid_type', + status: 422, + }) + ); + await deleteApplicationRole(applicationId, roleId); + + ctx.status = 204; + + return next(); + } + ); +} diff --git a/packages/core/src/routes/init.ts b/packages/core/src/routes/init.ts index 1659ec404..2c2a03e65 100644 --- a/packages/core/src/routes/init.ts +++ b/packages/core/src/routes/init.ts @@ -14,6 +14,7 @@ import adminUserRoleRoutes from './admin-user-role.js'; import adminUserSearchRoutes from './admin-user-search.js'; import adminUserSocialRoutes from './admin-user-social.js'; import adminUserRoutes from './admin-user.js'; +import applicationRoleRoutes from './application-role.js'; import applicationRoutes from './application.js'; import authnRoutes from './authn.js'; import connectorRoutes from './connector/index.js'; @@ -44,6 +45,7 @@ const createRouters = (tenant: TenantContext) => { managementRouter.use(koaTenantGuard(tenant.cloudConnection)); applicationRoutes(managementRouter, tenant); + applicationRoleRoutes(managementRouter, tenant); logtoConfigRoutes(managementRouter, tenant); connectorRoutes(managementRouter, tenant); resourceRoutes(managementRouter, tenant); diff --git a/packages/integration-tests/src/api/application.ts b/packages/integration-tests/src/api/application.ts index 6d1476e84..cad136fff 100644 --- a/packages/integration-tests/src/api/application.ts +++ b/packages/integration-tests/src/api/application.ts @@ -3,6 +3,7 @@ import type { CreateApplication, ApplicationType, OidcClientMetadata, + Role, } from '@logto/schemas'; import { authedAdminApi } from './api.js'; @@ -45,3 +46,19 @@ export const updateApplication = async ( export const deleteApplication = async (applicationId: string) => authedAdminApi.delete(`applications/${applicationId}`); + +export const getApplicationRoles = async (applicationId: string) => + authedAdminApi.get(`applications/${applicationId}/roles`).json(); + +export const assignRolesToApplication = async (applicationId: string, roleIds: string[]) => + authedAdminApi.post(`applications/${applicationId}/roles`, { + json: { roleIds }, + }); + +export const putRolesToApplication = async (applicationId: string, roleIds: string[]) => + authedAdminApi.put(`applications/${applicationId}/roles`, { + json: { roleIds }, + }); + +export const deleteRoleFromApplication = async (applicationId: string, roleId: string) => + authedAdminApi.delete(`applications/${applicationId}/roles/${roleId}`); diff --git a/packages/integration-tests/src/tests/api/application.roles.test.ts b/packages/integration-tests/src/tests/api/application.roles.test.ts new file mode 100644 index 000000000..80b62fb72 --- /dev/null +++ b/packages/integration-tests/src/tests/api/application.roles.test.ts @@ -0,0 +1,110 @@ +import { ApplicationType, RoleType } from '@logto/schemas'; +import { generateStandardId } from '@logto/shared'; +import { HTTPError } from 'got'; + +import { + createApplication, + getApplicationRoles, + assignRolesToApplication, + deleteRoleFromApplication, + putRolesToApplication, +} from '#src/api/index.js'; +import { createRole } from '#src/api/role.js'; +import { expectRejects } from '#src/helpers/index.js'; + +describe('admin console application management (roles)', () => { + it('should get empty list successfully', async () => { + const applicationType = ApplicationType.MachineToMachine; + const application = await createApplication(generateStandardId(), applicationType); + + const applicationRoles = await getApplicationRoles(application.id); + expect(applicationRoles.length).toBe(0); + }); + + it('throws when trying to get roles of non-m2m app', async () => { + const applicationType = ApplicationType.SPA; + const application = await createApplication(generateStandardId(), applicationType); + + const response = await getApplicationRoles(application.id).catch((error: unknown) => error); + expect(response instanceof HTTPError && response.response.statusCode === 422).toBe(true); + }); + + it('should assign roles to app and get list successfully', async () => { + const applicationType = ApplicationType.MachineToMachine; + const application = await createApplication(generateStandardId(), applicationType); + const role1 = await createRole({ type: RoleType.MachineToMachine }); + const role2 = await createRole({ type: RoleType.MachineToMachine }); + + await assignRolesToApplication(application.id, [role1.id, role2.id]); + const roles = await getApplicationRoles(application.id); + expect(roles.length).toBe(2); + expect(roles.find(({ id }) => id === role1.id)).toBeDefined(); + expect(roles.find(({ id }) => id === role2.id)).toBeDefined(); + }); + + it('should fail when assign duplicated role to app', async () => { + const applicationType = ApplicationType.MachineToMachine; + const application = await createApplication(generateStandardId(), applicationType); + const role = await createRole({ type: RoleType.MachineToMachine }); + + await assignRolesToApplication(application.id, [role.id]); + await expectRejects(assignRolesToApplication(application.id, [role.id]), { + code: 'application.role_exists', + statusCode: 422, + }); + }); + + it('should fail when assign role to non-m2m app', async () => { + const applicationType = ApplicationType.SPA; + const application = await createApplication(generateStandardId(), applicationType); + const role = await createRole({ type: RoleType.MachineToMachine }); + + await expectRejects(assignRolesToApplication(application.id, [role.id]), { + code: 'application.invalid_type', + statusCode: 422, + }); + }); + + it('should put roles to app successfully', async () => { + const applicationType = ApplicationType.MachineToMachine; + const application = await createApplication(generateStandardId(), applicationType); + const role1 = await createRole({ type: RoleType.MachineToMachine }); + const role2 = await createRole({ type: RoleType.MachineToMachine }); + const role3 = await createRole({ type: RoleType.MachineToMachine }); + + await assignRolesToApplication(application.id, [role2.id]); + const roles = await getApplicationRoles(application.id); + expect(roles.length).toBe(1); + expect(roles.find(({ id }) => id === role2.id)).toBeDefined(); + + await putRolesToApplication(application.id, [role1.id, role3.id]); + const updatedRoles = await getApplicationRoles(application.id); + expect(updatedRoles.length).toBe(2); + expect(updatedRoles.find(({ id }) => id === role1.id)).toBeDefined(); + expect(updatedRoles.find(({ id }) => id === role3.id)).toBeDefined(); + }); + + it('should delete role from app successfully', async () => { + const applicationType = ApplicationType.MachineToMachine; + const application = await createApplication(generateStandardId(), applicationType); + const role1 = await createRole({ type: RoleType.MachineToMachine }); + const role2 = await createRole({ type: RoleType.MachineToMachine }); + + await assignRolesToApplication(application.id, [role1.id, role2.id]); + await deleteRoleFromApplication(application.id, role1.id); + + const roles = await getApplicationRoles(application.id); + expect(roles.length).toBe(1); + }); + + it('should failed to delete non-existing-role from app', async () => { + const applicationType = ApplicationType.MachineToMachine; + const application = await createApplication(generateStandardId(), applicationType); + const role = await createRole({ type: RoleType.MachineToMachine }); + + const response = await deleteRoleFromApplication(application.id, role.id).catch( + (error: unknown) => error + ); + expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true); + }); +}); diff --git a/packages/phrases/src/locales/de/errors/application.ts b/packages/phrases/src/locales/de/errors/application.ts new file mode 100644 index 000000000..4175564f3 --- /dev/null +++ b/packages/phrases/src/locales/de/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Nur Maschinen-zu-Maschinen-Anwendungen können Rollen haben.', + role_exists: 'Die Rolle mit der ID {{roleId}} wurde bereits dieser Anwendung hinzugefügt.', + invalid_role_type: + 'Es ist nicht möglich, einer Maschinen-zu-Maschinen-Anwendung eine Benutzertyp-Rolle zuzuweisen.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/de/errors/index.ts b/packages/phrases/src/locales/de/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/de/errors/index.ts +++ b/packages/phrases/src/locales/de/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/en/errors/application.ts b/packages/phrases/src/locales/en/errors/application.ts new file mode 100644 index 000000000..595b4fb1b --- /dev/null +++ b/packages/phrases/src/locales/en/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: 'Only machine to machine applications can have associated roles.', + role_exists: 'The role id {{roleId}} is already been added to this application.', + invalid_role_type: 'Can not assign user type role to machine to machine application.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/en/errors/index.ts b/packages/phrases/src/locales/en/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/en/errors/index.ts +++ b/packages/phrases/src/locales/en/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/es/errors/application.ts b/packages/phrases/src/locales/es/errors/application.ts new file mode 100644 index 000000000..c0775811f --- /dev/null +++ b/packages/phrases/src/locales/es/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Solo las aplicaciones de máquina a máquina pueden tener roles asociados.', + role_exists: 'La identificación del rol {{roleId}} ya se ha agregado a esta aplicación.', + invalid_role_type: + 'No se puede asignar un rol de tipo usuario a una aplicación de máquina a máquina.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/es/errors/index.ts b/packages/phrases/src/locales/es/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/es/errors/index.ts +++ b/packages/phrases/src/locales/es/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/fr/errors/application.ts b/packages/phrases/src/locales/fr/errors/application.ts new file mode 100644 index 000000000..672b38608 --- /dev/null +++ b/packages/phrases/src/locales/fr/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Seules les applications machine à machine peuvent avoir des rôles associés.', + role_exists: "Le rôle d'identifiant {{roleId}} a déjà été ajouté à cette application.", + invalid_role_type: + "Impossible d'assigner un rôle de type utilisateur à une application machine à machine.", +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/fr/errors/index.ts b/packages/phrases/src/locales/fr/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/fr/errors/index.ts +++ b/packages/phrases/src/locales/fr/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/it/errors/application.ts b/packages/phrases/src/locales/it/errors/application.ts new file mode 100644 index 000000000..f3a7ce494 --- /dev/null +++ b/packages/phrases/src/locales/it/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Solo le applicazioni da macchina a macchina possono avere ruoli associati.', + role_exists: "L'ID ruolo {{roleId}} è già stato aggiunto a questa applicazione.", + invalid_role_type: + "Impossibile assegnare un ruolo di tipo utente all'applicazione da macchina a macchina.", +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/it/errors/index.ts b/packages/phrases/src/locales/it/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/it/errors/index.ts +++ b/packages/phrases/src/locales/it/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/ja/errors/application.ts b/packages/phrases/src/locales/ja/errors/application.ts new file mode 100644 index 000000000..884a2e736 --- /dev/null +++ b/packages/phrases/src/locales/ja/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: '関連するロールを持つことができるのは、マシン間アプリケーションのみです。', + role_exists: 'ロールID {{roleId}} は、すでにこのアプリケーションに追加されています。', + invalid_role_type: + 'ユーザータイプのロールをマシン間アプリケーションに割り当てることはできません。', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/ja/errors/index.ts b/packages/phrases/src/locales/ja/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/ja/errors/index.ts +++ b/packages/phrases/src/locales/ja/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/ko/errors/application.ts b/packages/phrases/src/locales/ko/errors/application.ts new file mode 100644 index 000000000..0649a9fda --- /dev/null +++ b/packages/phrases/src/locales/ko/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: '관련 역할을 가질 수 있는 것은 기계 대 기계 응용 프로그램만 가능합니다.', + role_exists: '역할 ID {{roleId}} 가 이미이 응용 프로그램에 추가되었습니다.', + invalid_role_type: '사용자 유형 역할을 기계 대 기계 응용 프로그램에 할당할 수 없습니다.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/ko/errors/index.ts b/packages/phrases/src/locales/ko/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/ko/errors/index.ts +++ b/packages/phrases/src/locales/ko/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/pl-pl/errors/application.ts b/packages/phrases/src/locales/pl-pl/errors/application.ts new file mode 100644 index 000000000..3e82d1a7a --- /dev/null +++ b/packages/phrases/src/locales/pl-pl/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: 'Tylko aplikacje maszyna-do-maszyny mogą mieć przypisane role.', + role_exists: 'Rola o identyfikatorze {{roleId}} została już dodana do tej aplikacji.', + invalid_role_type: 'Nie można przypisać roli typu użytkownika do aplikacji maszyna-do-maszyny.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/pl-pl/errors/index.ts b/packages/phrases/src/locales/pl-pl/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/pl-pl/errors/index.ts +++ b/packages/phrases/src/locales/pl-pl/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/pt-br/errors/application.ts b/packages/phrases/src/locales/pt-br/errors/application.ts new file mode 100644 index 000000000..365e3fcf1 --- /dev/null +++ b/packages/phrases/src/locales/pt-br/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Apenas aplicações de máquina para máquina podem ter funções associadas.', + role_exists: 'O id da função {{roleId}} já foi adicionado a este aplicativo.', + invalid_role_type: + 'Não é possível atribuir uma função de tipo de usuário a um aplicativo de máquina para máquina.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/pt-br/errors/index.ts b/packages/phrases/src/locales/pt-br/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/pt-br/errors/index.ts +++ b/packages/phrases/src/locales/pt-br/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/pt-pt/errors/application.ts b/packages/phrases/src/locales/pt-pt/errors/application.ts new file mode 100644 index 000000000..cdff351a6 --- /dev/null +++ b/packages/phrases/src/locales/pt-pt/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Apenas aplicações máquina a máquina podem ter funções associadas.', + role_exists: 'O id da função {{roleId}} já foi adicionado a esta aplicação.', + invalid_role_type: + 'Não é possível atribuir uma função de tipo de utilizador a uma aplicação máquina a máquina.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/pt-pt/errors/index.ts b/packages/phrases/src/locales/pt-pt/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/pt-pt/errors/index.ts +++ b/packages/phrases/src/locales/pt-pt/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/ru/errors/application.ts b/packages/phrases/src/locales/ru/errors/application.ts new file mode 100644 index 000000000..3178c0e75 --- /dev/null +++ b/packages/phrases/src/locales/ru/errors/application.ts @@ -0,0 +1,8 @@ +const application = { + invalid_type: 'Только приложения типа "от машины к машине" могут иметь связанные роли.', + role_exists: 'Роль с идентификатором {{roleId}} уже добавлена в это приложение.', + invalid_role_type: + 'Невозможно назначить роль типа "пользователь" для приложения типа "от машины к машине".', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/ru/errors/index.ts b/packages/phrases/src/locales/ru/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/ru/errors/index.ts +++ b/packages/phrases/src/locales/ru/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/tr-tr/errors/application.ts b/packages/phrases/src/locales/tr-tr/errors/application.ts new file mode 100644 index 000000000..2322812e1 --- /dev/null +++ b/packages/phrases/src/locales/tr-tr/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: 'Sadece makine ile makine uygulamaları rollerle ilişkilendirilebilir.', + role_exists: 'Bu uygulamaya zaten {{roleId}} kimlikli bir rol eklenmiş.', + invalid_role_type: 'Kullanıcı tipi rolü makine ile makine uygulamasına atayamaz.', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/tr-tr/errors/index.ts b/packages/phrases/src/locales/tr-tr/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/tr-tr/errors/index.ts +++ b/packages/phrases/src/locales/tr-tr/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/zh-cn/errors/application.ts b/packages/phrases/src/locales/zh-cn/errors/application.ts new file mode 100644 index 000000000..77c502079 --- /dev/null +++ b/packages/phrases/src/locales/zh-cn/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: '只有机器对机器应用程序可以有关联角色。', + role_exists: '角色 ID {{roleId}} 已添加到此应用程序。', + invalid_role_type: '无法将用户类型角色分配给机器对机器应用程序。', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/zh-cn/errors/index.ts b/packages/phrases/src/locales/zh-cn/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/zh-cn/errors/index.ts +++ b/packages/phrases/src/locales/zh-cn/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/zh-hk/errors/application.ts b/packages/phrases/src/locales/zh-hk/errors/application.ts new file mode 100644 index 000000000..e087c636a --- /dev/null +++ b/packages/phrases/src/locales/zh-hk/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: '只有機器對機器應用程式才能有相關職能。', + role_exists: '角色 ID {{roleId}} 已經被添加到此應用程式中。', + invalid_role_type: '無法將使用者類型的角色分配給機器對機器應用程式。', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/zh-hk/errors/index.ts b/packages/phrases/src/locales/zh-hk/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/zh-hk/errors/index.ts +++ b/packages/phrases/src/locales/zh-hk/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/zh-tw/errors/application.ts b/packages/phrases/src/locales/zh-tw/errors/application.ts new file mode 100644 index 000000000..34887d65c --- /dev/null +++ b/packages/phrases/src/locales/zh-tw/errors/application.ts @@ -0,0 +1,7 @@ +const application = { + invalid_type: '僅允許機器對機器應用程式附加角色。', + role_exists: '該角色 ID {{roleId}} 已被添加至此應用程式。', + invalid_role_type: '無法將使用者類型的角色指派給機器對機器應用程式。', +}; + +export default Object.freeze(application); diff --git a/packages/phrases/src/locales/zh-tw/errors/index.ts b/packages/phrases/src/locales/zh-tw/errors/index.ts index fd4e19478..f4c8f1ed2 100644 --- a/packages/phrases/src/locales/zh-tw/errors/index.ts +++ b/packages/phrases/src/locales/zh-tw/errors/index.ts @@ -1,3 +1,4 @@ +import application from './application.js'; import auth from './auth.js'; import connector from './connector.js'; import domain from './domain.js'; @@ -42,6 +43,7 @@ const errors = { hook, domain, subscription, + application, }; export default Object.freeze(errors);