Skip to content

Commit

Permalink
chore: launch m2m app for organizations
Browse files Browse the repository at this point in the history
  • Loading branch information
gao-sun committed Jun 28, 2024
1 parent f8f84f5 commit f1b6c38
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 137 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { condArray } from '@silverhand/essentials';
import { Navigate, type RouteObject } from 'react-router-dom';

import { isDevFeaturesEnabled } from '@/consts/env';
import OrganizationDetails from '@/pages/OrganizationDetails';
import MachineToMachine from '@/pages/OrganizationDetails/MachineToMachine';
import Members from '@/pages/OrganizationDetails/Members';
Expand All @@ -17,15 +16,15 @@ export const organizations: RouteObject = {
{
path: ':id/*',
element: <OrganizationDetails />,
children: condArray(
children: [
{ index: true, element: <Navigate replace to={OrganizationDetailsTabs.Settings} /> },
{ path: OrganizationDetailsTabs.Settings, element: <Settings /> },
{ path: OrganizationDetailsTabs.Members, element: <Members /> },
isDevFeaturesEnabled && {
{
path: OrganizationDetailsTabs.MachineToMachine,
element: <MachineToMachine />,
}
),
},
],
}
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
import OrganizationList from '@/components/OrganizationList';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import { ApplicationDetailsTabs, logtoThirdPartyGuideLink, protectedAppLink } from '@/consts';
import { isDevFeaturesEnabled } from '@/consts/env';
import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import TabWrapper from '@/ds-components/TabWrapper';
Expand Down Expand Up @@ -178,11 +177,9 @@ function ApplicationDetailsContent({ data, oidcConfig, onApplicationUpdated }: P
<TabNavItem href={`/applications/${data.id}/${ApplicationDetailsTabs.Logs}`}>
{t('application_details.machine_logs')}
</TabNavItem>
{isDevFeaturesEnabled && (
<TabNavItem href={`/applications/${data.id}/${ApplicationDetailsTabs.Organizations}`}>
{t('organizations.title')}
</TabNavItem>
)}
<TabNavItem href={`/applications/${data.id}/${ApplicationDetailsTabs.Organizations}`}>
{t('organizations.title')}
</TabNavItem>
</>
)}
{data.isThirdParty && (
Expand Down
11 changes: 4 additions & 7 deletions packages/console/src/pages/OrganizationDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import Skeleton from '@/components/DetailsPage/Skeleton';
import Drawer from '@/components/Drawer';
import PageMeta from '@/components/PageMeta';
import ThemedIcon from '@/components/ThemedIcon';
import { isDevFeaturesEnabled } from '@/consts/env';
import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import useApi, { type RequestError } from '@/hooks/use-api';
Expand Down Expand Up @@ -134,12 +133,10 @@ function OrganizationDetails() {
<TabNavItem href={`${pathname}/${id}/${OrganizationDetailsTabs.Members}`}>
{t('organizations.members')}
</TabNavItem>
{/* TODO: Remove */}
{isDevFeaturesEnabled && (
<TabNavItem href={`${pathname}/${id}/${OrganizationDetailsTabs.MachineToMachine}`}>
{t('organizations.machine_to_machine')}
</TabNavItem>
)}

<TabNavItem href={`${pathname}/${id}/${OrganizationDetailsTabs.MachineToMachine}`}>
{t('organizations.machine_to_machine')}
</TabNavItem>
</TabNav>
<Outlet
context={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';

import { isDevFeaturesEnabled } from '@/consts/env';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import FormField from '@/ds-components/FormField';
Expand Down Expand Up @@ -105,29 +104,26 @@ function CreateOrganizationRoleModal({ isOpen, onClose }: Props) {
{...register('description')}
/>
</FormField>
{/* TODO: Remove */}
{isDevFeaturesEnabled && (
<FormField title="organization_template.roles.create_modal.type">
<Controller
name="type"
control={control}
render={({ field: { onChange, value, name } }) => (
<RadioGroup
name={name}
className={styles.roleTypes}
value={value}
onChange={(value) => {
onChange(value);
}}
>
{radioOptions.map(({ key, value }) => (
<Radio key={value} title={<DynamicT forKey={key} />} value={value} />
))}
</RadioGroup>
)}
/>
</FormField>
)}
<FormField title="organization_template.roles.create_modal.type">
<Controller
name="type"
control={control}
render={({ field: { onChange, value, name } }) => (
<RadioGroup
name={name}
className={styles.roleTypes}
value={value}
onChange={(value) => {
onChange(value);
}}
>
{radioOptions.map(({ key, value }) => (
<Radio key={value} title={<DynamicT forKey={key} />} value={value} />
))}
</RadioGroup>
)}
/>
</FormField>
</ModalLayout>
</ReactModal>
);
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/oidc/grants/client-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import dpopValidate from 'oidc-provider/lib/helpers/validate_dpop.js';
import instance from 'oidc-provider/lib/helpers/weak_cache.js';
import checkResource from 'oidc-provider/lib/shared/check_resource.js';

import { EnvSet } from '#src/env-set/index.js';
import { type EnvSet } from '#src/env-set/index.js';
import type Queries from '#src/tenants/Queries.js';
import assertThat from '#src/utils/assert-that.js';

Expand Down Expand Up @@ -68,10 +68,6 @@ export const buildHandler: (
// The value type is `unknown`, which will swallow other type inferences. So we have to cast it
// to `Boolean` first.
const organizationId = cond(Boolean(params?.organization_id) && String(params?.organization_id));
// TODO: Remove
if (!EnvSet.values.isDevFeaturesEnabled && organizationId) {
throw new InvalidTarget('organization tokens are not supported yet');
}

if (
organizationId &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"paths": {
"/api/applications/{id}/organizations": {
"get": {
"tags": ["Dev feature"],
"summary": "Get application organizations",
"description": "Get the list of organizations that an application is associated with.",
"responses": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { organizationWithOrganizationRolesGuard } from '@logto/schemas';
import { z } from 'zod';

import { EnvSet } from '#src/env-set/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';

Expand All @@ -10,11 +9,6 @@ import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
export default function applicationOrganizationRoutes<T extends ManagementApiRouter>(
...[router, { queries }]: RouterInitArgs<T>
) {
// TODO: Remove
if (!EnvSet.values.isDevFeaturesEnabled) {
return;
}

router.get(
'/applications/:id/organizations',
koaPagination({ isOptional: true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
{
"name": "Organization applications",
"description": "Manage organization - application relationships. An application can be associated with one or more organizations in order to get access to the organization resources.\n\nCurrently, only machine-to-machine applications can be associated with organizations."
},
{ "name": "Dev feature" }
}
],
"paths": {
"/api/organizations/{id}/applications": {
Expand Down Expand Up @@ -81,7 +80,6 @@
},
"/api/organizations/{id}/applications/roles": {
"post": {
"tags": ["Dev feature"],
"summary": "Assign roles to applications in an organization",
"description": "Assign roles to applications in the specified organization.",
"requestBody": {
Expand Down
111 changes: 54 additions & 57 deletions packages/core/src/routes/organization/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '@logto/schemas';
import { z } from 'zod';

import { EnvSet } from '#src/env-set/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import { applicationSearchKeys } from '#src/queries/application.js';
Expand All @@ -21,69 +20,67 @@ export default function applicationRoutes(
router: SchemaRouter<OrganizationKeys, CreateOrganization, Organization>,
organizations: OrganizationQueries
) {
if (EnvSet.values.isDevFeaturesEnabled) {
// MARK: Organization - application relation routes
router.addRelationRoutes(organizations.relations.apps, undefined, {
disabled: { get: true },
hookEvent: 'Organization.Membership.Updated',
});
// MARK: Organization - application relation routes
router.addRelationRoutes(organizations.relations.apps, undefined, {
disabled: { get: true },
hookEvent: 'Organization.Membership.Updated',
});

router.get(
'/:id/applications',
koaPagination(),
koaGuard({
query: z.object({ q: z.string().optional() }),
params: z.object({ id: z.string().min(1) }),
response: applicationWithOrganizationRolesGuard.array(),
status: [200, 404],
}),
async (ctx, next) => {
const search = parseSearchOptions(applicationSearchKeys, ctx.guard.query);
router.get(
'/:id/applications',
koaPagination(),
koaGuard({
query: z.object({ q: z.string().optional() }),
params: z.object({ id: z.string().min(1) }),
response: applicationWithOrganizationRolesGuard.array(),
status: [200, 404],
}),
async (ctx, next) => {
const search = parseSearchOptions(applicationSearchKeys, ctx.guard.query);

Check warning on line 39 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L39

Added line #L39 was not covered by tests

const [totalCount, entities] =
await organizations.relations.apps.getApplicationsByOrganizationId(
ctx.guard.params.id,
ctx.pagination,
search
);
const [totalCount, entities] =
await organizations.relations.apps.getApplicationsByOrganizationId(
ctx.guard.params.id,
ctx.pagination,
search
);

Check warning on line 46 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L41-L46

Added lines #L41 - L46 were not covered by tests

ctx.pagination.totalCount = totalCount;
ctx.body = entities;
ctx.pagination.totalCount = totalCount;
ctx.body = entities;

Check warning on line 49 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L48-L49

Added lines #L48 - L49 were not covered by tests

return next();
}
);
return next();
}

Check warning on line 52 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L51-L52

Added lines #L51 - L52 were not covered by tests
);

router.post(
'/:id/applications/roles',
koaGuard({
params: z.object({ id: z.string().min(1) }),
body: z.object({
applicationIds: z.string().min(1).array().nonempty(),
organizationRoleIds: z.string().min(1).array().nonempty(),
}),
status: [201, 422],
router.post(
'/:id/applications/roles',
koaGuard({
params: z.object({ id: z.string().min(1) }),
body: z.object({
applicationIds: z.string().min(1).array().nonempty(),
organizationRoleIds: z.string().min(1).array().nonempty(),
}),
async (ctx, next) => {
const { id } = ctx.guard.params;
const { applicationIds, organizationRoleIds } = ctx.guard.body;
status: [201, 422],
}),
async (ctx, next) => {
const { id } = ctx.guard.params;
const { applicationIds, organizationRoleIds } = ctx.guard.body;

Check warning on line 67 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L66-L67

Added lines #L66 - L67 were not covered by tests

await organizations.relations.appsRoles.insert(
...organizationRoleIds.flatMap((organizationRoleId) =>
applicationIds.map((applicationId) => ({
organizationId: id,
applicationId,
organizationRoleId,
}))
)
);
await organizations.relations.appsRoles.insert(
...organizationRoleIds.flatMap((organizationRoleId) =>
applicationIds.map((applicationId) => ({
organizationId: id,
applicationId,
organizationRoleId,
}))
)
);

Check warning on line 77 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L69-L77

Added lines #L69 - L77 were not covered by tests

ctx.status = 201;
return next();
}
);
ctx.status = 201;
return next();
}

Check warning on line 81 in packages/core/src/routes/organization/application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/organization/application/index.ts#L79-L81

Added lines #L79 - L81 were not covered by tests
);

// MARK: Organization - application role relation routes
applicationRoleRelationRoutes(router, organizations);
}
// MARK: Organization - application role relation routes
applicationRoleRelationRoutes(router, organizations);
}
2 changes: 1 addition & 1 deletion packages/core/src/routes/swagger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const identifiableEntityNames = Object.freeze([
/** Additional tags that cannot be inferred from the path. */
const additionalTags = Object.freeze(
condArray<string>(
EnvSet.values.isDevFeaturesEnabled && 'Organization applications',
'Organization applications',
EnvSet.values.isDevFeaturesEnabled && 'Security',
'Organization users'
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
getOrganizations,
} from '#src/api/application.js';
import { OrganizationApiTest } from '#src/helpers/organization.js';
import { devFeatureTest, generateTestName } from '#src/utils.js';
import { generateTestName } from '#src/utils.js';

devFeatureTest.describe('application organizations', () => {
describe('application organizations', () => {
const organizationApi = new OrganizationApiTest();
const applications: Application[] = [];
const createApplication = async (...args: Parameters<typeof createApplicationApi>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import {
} from '#src/api/resource.js';
import { assignScopesToRole, createRole as createRoleApi, deleteRole } from '#src/api/role.js';
import { createScope as createScopeApi } from '#src/api/scope.js';
import { isDevFeaturesEnabled, logtoUrl } from '#src/constants.js';
import { logtoUrl } from '#src/constants.js';
import { OrganizationApiTest } from '#src/helpers/organization.js';
import { devFeatureTest, randomString } from '#src/utils.js';
import { randomString } from '#src/utils.js';

type TokenResponse = {
access_token: string;
Expand Down Expand Up @@ -175,19 +175,6 @@ describe('client credentials grant', () => {
});

describe('organization token', () => {
it('should fail if dev feature is not enabled', async () => {
if (isDevFeaturesEnabled) {
return;
}

await expectError({ organization_id: 'not-found' }, 400, {
error: 'invalid_target',
error_description: 'organization tokens are not supported yet',
});
});
});

devFeatureTest.describe('organization token', () => {
it('should fail if the application is not associated with the organization', async () => {
await expectError({ organization_id: 'not-found' }, 403, {
error: 'access_denied',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import {
deleteApplication,
} from '#src/api/application.js';
import { OrganizationApiTest } from '#src/helpers/organization.js';
import { devFeatureTest, generateTestName } from '#src/utils.js';
import { generateTestName } from '#src/utils.js';

// TODO: Remove this prefix
devFeatureTest.describe('organization application APIs', () => {
describe('organization application APIs', () => {
describe('organization get applications', () => {
const organizationApi = new OrganizationApiTest();
const applications: Application[] = [];
Expand Down
Loading

0 comments on commit f1b6c38

Please sign in to comment.