0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(core,test,phrases): add application-role related APIs (#4425)

This commit is contained in:
Darcy Ye 2023-09-11 14:06:40 +08:00 committed by GitHub
parent 285aa745e7
commit 9251c2bb40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 584 additions and 0 deletions

View file

@ -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',
};

View file

@ -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
);
});
});

View file

@ -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<T extends AuthedRouter>(
...[router, { queries }]: RouterInitArgs<T>
) {
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();
}
);
}

View file

@ -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);

View file

@ -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<Role[]>();
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}`);

View file

@ -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);
});
});

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1,8 @@
const application = {
invalid_type: '関連するロールを持つことができるのは、マシン間アプリケーションのみです。',
role_exists: 'ロールID {{roleId}} は、すでにこのアプリケーションに追加されています。',
invalid_role_type:
'ユーザータイプのロールをマシン間アプリケーションに割り当てることはできません。',
};
export default Object.freeze(application);

View file

@ -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);

View file

@ -0,0 +1,7 @@
const application = {
invalid_type: '관련 역할을 가질 수 있는 것은 기계 대 기계 응용 프로그램만 가능합니다.',
role_exists: '역할 ID {{roleId}} 가 이미이 응용 프로그램에 추가되었습니다.',
invalid_role_type: '사용자 유형 역할을 기계 대 기계 응용 프로그램에 할당할 수 없습니다.',
};
export default Object.freeze(application);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1,8 @@
const application = {
invalid_type: 'Только приложения типа "от машины к машине" могут иметь связанные роли.',
role_exists: 'Роль с идентификатором {{roleId}} уже добавлена в это приложение.',
invalid_role_type:
'Невозможно назначить роль типа "пользователь" для приложения типа "от машины к машине".',
};
export default Object.freeze(application);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1,7 @@
const application = {
invalid_type: '只有机器对机器应用程序可以有关联角色。',
role_exists: '角色 ID {{roleId}} 已添加到此应用程序。',
invalid_role_type: '无法将用户类型角色分配给机器对机器应用程序。',
};
export default Object.freeze(application);

View file

@ -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);

View file

@ -0,0 +1,7 @@
const application = {
invalid_type: '只有機器對機器應用程式才能有相關職能。',
role_exists: '角色 ID {{roleId}} 已經被添加到此應用程式中。',
invalid_role_type: '無法將使用者類型的角色分配給機器對機器應用程式。',
};
export default Object.freeze(application);

View file

@ -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);

View file

@ -0,0 +1,7 @@
const application = {
invalid_type: '僅允許機器對機器應用程式附加角色。',
role_exists: '該角色 ID {{roleId}} 已被添加至此應用程式。',
invalid_role_type: '無法將使用者類型的角色指派給機器對機器應用程式。',
};
export default Object.freeze(application);

View file

@ -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);