mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core,schemas): add org resource scopes to consent get (#5808)
This commit is contained in:
parent
f57e21fc20
commit
726a65dd8e
6 changed files with 227 additions and 56 deletions
|
@ -175,15 +175,18 @@ export const createUserLibrary = (queries: Queries) => {
|
||||||
const findUserScopesForResourceIndicator = async (
|
const findUserScopesForResourceIndicator = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
resourceIndicator: string,
|
resourceIndicator: string,
|
||||||
|
findFromOrganizations = false,
|
||||||
organizationId?: string
|
organizationId?: string
|
||||||
): Promise<readonly Scope[]> => {
|
): Promise<readonly Scope[]> => {
|
||||||
const usersRoles = await findUsersRolesByUserId(userId);
|
const usersRoles = await findUsersRolesByUserId(userId);
|
||||||
const rolesScopes = await findRolesScopesByRoleIds(usersRoles.map(({ roleId }) => roleId));
|
const rolesScopes = await findRolesScopesByRoleIds(usersRoles.map(({ roleId }) => roleId));
|
||||||
const organizationScopes = await organizations.relations.rolesUsers.getUserResourceScopes(
|
const organizationScopes = findFromOrganizations
|
||||||
userId,
|
? await organizations.relations.rolesUsers.getUserResourceScopes(
|
||||||
resourceIndicator,
|
userId,
|
||||||
organizationId
|
resourceIndicator,
|
||||||
);
|
organizationId
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
const scopes = await findScopesByIdsAndResourceIndicator(
|
const scopes = await findScopesByIdsAndResourceIndicator(
|
||||||
[...rolesScopes.map(({ scopeId }) => scopeId), ...organizationScopes.map(({ id }) => id)],
|
[...rolesScopes.map(({ scopeId }) => scopeId), ...organizationScopes.map(({ id }) => id)],
|
||||||
|
|
|
@ -143,7 +143,9 @@ export default function initOidc(
|
||||||
|
|
||||||
const { accessTokenTtl: accessTokenTTL } = resourceServer;
|
const { accessTokenTtl: accessTokenTTL } = resourceServer;
|
||||||
|
|
||||||
const { client, params } = ctx.oidc;
|
const { client, params, session, entities } = ctx.oidc;
|
||||||
|
const userId = session?.accountId ?? entities.Account?.accountId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In consent or code exchange flow, the organization_id is undefined,
|
* In consent or code exchange flow, the organization_id is undefined,
|
||||||
* and all the scopes inherited from the all organization roles will be granted.
|
* and all the scopes inherited from the all organization roles will be granted.
|
||||||
|
@ -152,16 +154,19 @@ export default function initOidc(
|
||||||
* and will then narrow down the scopes to the specific organization.
|
* and will then narrow down the scopes to the specific organization.
|
||||||
*/
|
*/
|
||||||
const organizationId = params?.organization_id;
|
const organizationId = params?.organization_id;
|
||||||
const scopes = await findResourceScopes(
|
const scopes = await findResourceScopes({
|
||||||
queries,
|
queries,
|
||||||
libraries,
|
libraries,
|
||||||
ctx,
|
|
||||||
indicator,
|
indicator,
|
||||||
typeof organizationId === 'string' ? organizationId : undefined
|
findFromOrganizations: true,
|
||||||
);
|
organizationId: typeof organizationId === 'string' ? organizationId : undefined,
|
||||||
|
applicationId: client?.clientId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
// Need to filter out the unsupported scopes for the third-party application.
|
// Need to filter out the unsupported scopes for the third-party application.
|
||||||
if (client && (await isThirdPartyApplication(queries, client.clientId))) {
|
if (client && (await isThirdPartyApplication(queries, client.clientId))) {
|
||||||
|
// Get application consent resource scopes, from RBAC roles
|
||||||
const filteredScopes = await filterResourceScopesForTheThirdPartyApplication(
|
const filteredScopes = await filterResourceScopesForTheThirdPartyApplication(
|
||||||
libraries,
|
libraries,
|
||||||
client.clientId,
|
client.clientId,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ReservedResource } from '@logto/core-kit';
|
import { ReservedResource } from '@logto/core-kit';
|
||||||
import { type Resource } from '@logto/schemas';
|
import { type Resource } from '@logto/schemas';
|
||||||
import { trySafe, type Nullable } from '@silverhand/essentials';
|
import { trySafe, type Nullable } from '@silverhand/essentials';
|
||||||
import { type ResourceServer, type KoaContextWithOIDC } from 'oidc-provider';
|
import { type ResourceServer } from 'oidc-provider';
|
||||||
|
|
||||||
import { type EnvSet } from '#src/env-set/index.js';
|
import { EnvSet } from '#src/env-set/index.js';
|
||||||
import type Libraries from '#src/tenants/Libraries.js';
|
import type Libraries from '#src/tenants/Libraries.js';
|
||||||
import type Queries from '#src/tenants/Queries.js';
|
import type Queries from '#src/tenants/Queries.js';
|
||||||
|
|
||||||
|
@ -28,13 +28,23 @@ export const getSharedResourceServerData = (
|
||||||
*
|
*
|
||||||
* @see {@link ReservedResource} for the list of reserved resources.
|
* @see {@link ReservedResource} for the list of reserved resources.
|
||||||
*/
|
*/
|
||||||
export const findResourceScopes = async (
|
export const findResourceScopes = async ({
|
||||||
queries: Queries,
|
queries,
|
||||||
libraries: Libraries,
|
libraries,
|
||||||
ctx: KoaContextWithOIDC,
|
userId,
|
||||||
indicator: string,
|
applicationId,
|
||||||
organizationId?: string
|
indicator,
|
||||||
): Promise<ReadonlyArray<{ name: string; id: string }>> => {
|
organizationId,
|
||||||
|
findFromOrganizations,
|
||||||
|
}: {
|
||||||
|
queries: Queries;
|
||||||
|
libraries: Libraries;
|
||||||
|
indicator: string;
|
||||||
|
findFromOrganizations: boolean;
|
||||||
|
userId?: string;
|
||||||
|
applicationId?: string;
|
||||||
|
organizationId?: string;
|
||||||
|
}): Promise<ReadonlyArray<{ name: string; id: string }>> => {
|
||||||
if (isReservedResource(indicator)) {
|
if (isReservedResource(indicator)) {
|
||||||
switch (indicator) {
|
switch (indicator) {
|
||||||
case ReservedResource.Organization: {
|
case ReservedResource.Organization: {
|
||||||
|
@ -44,21 +54,22 @@ export const findResourceScopes = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { oidc } = ctx;
|
|
||||||
const {
|
const {
|
||||||
users: { findUserScopesForResourceIndicator },
|
users: { findUserScopesForResourceIndicator },
|
||||||
applications: { findApplicationScopesForResourceIndicator },
|
applications: { findApplicationScopesForResourceIndicator },
|
||||||
} = libraries;
|
} = libraries;
|
||||||
const userId = oidc.session?.accountId ?? oidc.entities.Account?.accountId;
|
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return findUserScopesForResourceIndicator(userId, indicator, organizationId);
|
return findUserScopesForResourceIndicator(
|
||||||
|
userId,
|
||||||
|
indicator,
|
||||||
|
findFromOrganizations,
|
||||||
|
organizationId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientId = oidc.entities.Client?.clientId;
|
if (applicationId) {
|
||||||
|
return findApplicationScopesForResourceIndicator(applicationId, indicator);
|
||||||
if (clientId) {
|
|
||||||
return findApplicationScopesForResourceIndicator(clientId, indicator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
@ -115,6 +126,7 @@ export const filterResourceScopesForTheThirdPartyApplication = async (
|
||||||
applications: {
|
applications: {
|
||||||
getApplicationUserConsentOrganizationScopes,
|
getApplicationUserConsentOrganizationScopes,
|
||||||
getApplicationUserConsentResourceScopes,
|
getApplicationUserConsentResourceScopes,
|
||||||
|
getApplicationUserConsentOrganizationResourceScopes,
|
||||||
},
|
},
|
||||||
} = libraries;
|
} = libraries;
|
||||||
|
|
||||||
|
@ -146,16 +158,20 @@ export const filterResourceScopesForTheThirdPartyApplication = async (
|
||||||
const userConsentResource = userConsentResources.find(
|
const userConsentResource = userConsentResources.find(
|
||||||
({ resource }) => resource.indicator === indicator
|
({ resource }) => resource.indicator === indicator
|
||||||
);
|
);
|
||||||
|
const userConsentOrganizationResources = EnvSet.values.isDevFeaturesEnabled
|
||||||
|
? await getApplicationUserConsentOrganizationResourceScopes(applicationId)
|
||||||
|
: [];
|
||||||
|
const userConsentOrganizationResource = userConsentOrganizationResources.find(
|
||||||
|
({ resource }) => resource.indicator === indicator
|
||||||
|
);
|
||||||
|
|
||||||
// If the resource is not in the application enabled user consent resources, return empty array
|
const resourceScopes = [
|
||||||
if (!userConsentResource) {
|
...(userConsentResource?.scopes ?? []),
|
||||||
return [];
|
...(userConsentOrganizationResource?.scopes ?? []),
|
||||||
}
|
];
|
||||||
|
|
||||||
const { scopes: userConsentResourceScopes } = userConsentResource;
|
|
||||||
|
|
||||||
return scopes.filter(({ id: resourceScopeId }) =>
|
return scopes.filter(({ id: resourceScopeId }) =>
|
||||||
userConsentResourceScopes.some(
|
resourceScopes.some(
|
||||||
({ id: consentResourceScopeId }) => consentResourceScopeId === resourceScopeId
|
({ id: consentResourceScopeId }) => consentResourceScopeId === resourceScopeId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
publicApplicationGuard,
|
publicApplicationGuard,
|
||||||
publicUserInfoGuard,
|
publicUserInfoGuard,
|
||||||
applicationSignInExperienceGuard,
|
applicationSignInExperienceGuard,
|
||||||
publicOrganizationGuard,
|
|
||||||
missingResourceScopesGuard,
|
missingResourceScopesGuard,
|
||||||
type ConsentInfoResponse,
|
type ConsentInfoResponse,
|
||||||
type MissingResourceScopes,
|
type MissingResourceScopes,
|
||||||
|
@ -16,8 +15,10 @@ import { type IRouterParamContext } from 'koa-router';
|
||||||
import { errors } from 'oidc-provider';
|
import { errors } from 'oidc-provider';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { EnvSet } from '#src/env-set/index.js';
|
||||||
import { consent, getMissingScopes } from '#src/libraries/session.js';
|
import { consent, getMissingScopes } from '#src/libraries/session.js';
|
||||||
import koaGuard from '#src/middleware/koa-guard.js';
|
import koaGuard from '#src/middleware/koa-guard.js';
|
||||||
|
import { findResourceScopes } from '#src/oidc/resource.js';
|
||||||
import type Queries from '#src/tenants/Queries.js';
|
import type Queries from '#src/tenants/Queries.js';
|
||||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||||
import assertThat from '#src/utils/assert-that.js';
|
import assertThat from '#src/utils/assert-that.js';
|
||||||
|
@ -96,16 +97,63 @@ const parseMissingResourceScopesInfo = async (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The missingResourceScopes in the prompt details are from `getResourceServerInfo`,
|
||||||
|
* which contains resource scopes and organization resource scopes.
|
||||||
|
* We need to separate the organization resource scopes from the resource scopes.
|
||||||
|
* The "scopes" in `missingResourceScopes` do not have "id", so we have to rebuild the scopes list first.
|
||||||
|
*/
|
||||||
|
const filterAndParseMissingResourceScopes = async ({
|
||||||
|
resourceScopes,
|
||||||
|
queries,
|
||||||
|
libraries,
|
||||||
|
userId,
|
||||||
|
organizationId,
|
||||||
|
}: {
|
||||||
|
resourceScopes: Record<string, string[]>;
|
||||||
|
queries: Queries;
|
||||||
|
libraries: TenantContext['libraries'];
|
||||||
|
userId: string;
|
||||||
|
organizationId?: string;
|
||||||
|
}) => {
|
||||||
|
const filteredResourceScopes = Object.fromEntries(
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(resourceScopes).map(
|
||||||
|
async ([resourceIndicator, missingScopes]): Promise<[string, string[]]> => {
|
||||||
|
if (!EnvSet.values.isDevFeaturesEnabled) {
|
||||||
|
return [resourceIndicator, missingScopes];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the list of scopes, `findFromOrganizations` is set to false,
|
||||||
|
// so it will only search the user resource scopes.
|
||||||
|
const scopes = await findResourceScopes({
|
||||||
|
queries,
|
||||||
|
libraries,
|
||||||
|
indicator: resourceIndicator,
|
||||||
|
userId,
|
||||||
|
findFromOrganizations: Boolean(organizationId),
|
||||||
|
organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
resourceIndicator,
|
||||||
|
missingScopes.filter((scope) => scopes.some(({ name }) => name === scope)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return parseMissingResourceScopesInfo(queries, filteredResourceScopes);
|
||||||
|
};
|
||||||
|
|
||||||
export default function consentRoutes<T extends IRouterParamContext>(
|
export default function consentRoutes<T extends IRouterParamContext>(
|
||||||
router: Router<unknown, WithInteractionDetailsContext<T>>,
|
router: Router<unknown, WithInteractionDetailsContext<T>>,
|
||||||
{
|
{ provider, queries, libraries }: TenantContext
|
||||||
provider,
|
|
||||||
queries,
|
|
||||||
libraries: {
|
|
||||||
applications: { validateUserConsentOrganizationMembership },
|
|
||||||
},
|
|
||||||
}: TenantContext
|
|
||||||
) {
|
) {
|
||||||
|
const {
|
||||||
|
applications: { validateUserConsentOrganizationMembership },
|
||||||
|
} = libraries;
|
||||||
const consentPath = `${interactionPrefix}/consent`;
|
const consentPath = `${interactionPrefix}/consent`;
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
|
@ -201,12 +249,42 @@ export default function consentRoutes<T extends IRouterParamContext>(
|
||||||
|
|
||||||
const userInfo = await queries.users.findUserById(accountId);
|
const userInfo = await queries.users.findUserById(accountId);
|
||||||
|
|
||||||
const { missingOIDCScope, missingResourceScopes } = getMissingScopes(prompt);
|
const { missingOIDCScope, missingResourceScopes: allMissingResourceScopes = {} } =
|
||||||
|
getMissingScopes(prompt);
|
||||||
|
|
||||||
|
// The missingResourceScopes from the prompt details are from `getResourceServerInfo`,
|
||||||
|
// which contains resource scopes and organization resource scopes.
|
||||||
|
// We need to separate the organization resource scopes from the resource scopes.
|
||||||
|
// The "scopes" in `missingResourceScopes` do not have "id", so we have to rebuild the scopes list.
|
||||||
|
const missingResourceScopes = await filterAndParseMissingResourceScopes({
|
||||||
|
resourceScopes: allMissingResourceScopes,
|
||||||
|
queries,
|
||||||
|
libraries,
|
||||||
|
userId: accountId,
|
||||||
|
});
|
||||||
|
|
||||||
// Find the organizations if the application is requesting the organizations scope
|
// Find the organizations if the application is requesting the organizations scope
|
||||||
const organizations = missingOIDCScope?.includes(UserScope.Organizations)
|
const organizations = missingOIDCScope?.includes(UserScope.Organizations)
|
||||||
? await queries.organizations.relations.users.getOrganizationsByUserId(accountId)
|
? await queries.organizations.relations.users.getOrganizationsByUserId(accountId)
|
||||||
: undefined;
|
: [];
|
||||||
|
|
||||||
|
const organizationsWithMissingResourceScopes = await Promise.all(
|
||||||
|
organizations.map(async ({ name, id }) => {
|
||||||
|
if (!EnvSet.values.isDevFeaturesEnabled) {
|
||||||
|
return { name, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingResourceScopes = await filterAndParseMissingResourceScopes({
|
||||||
|
resourceScopes: allMissingResourceScopes,
|
||||||
|
queries,
|
||||||
|
libraries,
|
||||||
|
userId: accountId,
|
||||||
|
organizationId: id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { name, id, missingResourceScopes };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
// Merge the public application data and application sign-in-experience data
|
// Merge the public application data and application sign-in-experience data
|
||||||
|
@ -218,15 +296,12 @@ export default function consentRoutes<T extends IRouterParamContext>(
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
user: publicUserInfoGuard.parse(userInfo),
|
user: publicUserInfoGuard.parse(userInfo),
|
||||||
organizations: organizations?.map((organization) =>
|
organizations: organizationsWithMissingResourceScopes,
|
||||||
publicOrganizationGuard.parse(organization)
|
|
||||||
),
|
|
||||||
// Filter out the OIDC scopes that are not needed for the consent page.
|
// Filter out the OIDC scopes that are not needed for the consent page.
|
||||||
missingOIDCScope: missingOIDCScope?.filter(
|
missingOIDCScope: missingOIDCScope?.filter(
|
||||||
(scope) => scope !== 'openid' && scope !== 'offline_access'
|
(scope) => scope !== 'openid' && scope !== 'offline_access'
|
||||||
),
|
),
|
||||||
// Parse the missing resource scopes info with details.
|
missingResourceScopes,
|
||||||
missingResourceScopes: await parseMissingResourceScopesInfo(queries, missingResourceScopes),
|
|
||||||
redirectUri,
|
redirectUri,
|
||||||
} satisfies ConsentInfoResponse;
|
} satisfies ConsentInfoResponse;
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,18 @@ import { assignUserConsentScopes } from '#src/api/application-user-consent-scope
|
||||||
import { createApplication, deleteApplication } from '#src/api/application.js';
|
import { createApplication, deleteApplication } from '#src/api/application.js';
|
||||||
import { getConsentInfo, putInteraction } from '#src/api/interaction.js';
|
import { getConsentInfo, putInteraction } from '#src/api/interaction.js';
|
||||||
import { OrganizationScopeApi } from '#src/api/organization-scope.js';
|
import { OrganizationScopeApi } from '#src/api/organization-scope.js';
|
||||||
|
import { createResource, deleteResource } from '#src/api/resource.js';
|
||||||
|
import { createScope } from '#src/api/scope.js';
|
||||||
import { initClient } from '#src/helpers/client.js';
|
import { initClient } from '#src/helpers/client.js';
|
||||||
|
import { OrganizationApiTest, OrganizationRoleApiTest } from '#src/helpers/organization.js';
|
||||||
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
|
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
|
||||||
import { generateNewUser } from '#src/helpers/user.js';
|
import { generateNewUser } from '#src/helpers/user.js';
|
||||||
|
import {
|
||||||
|
generateResourceIndicator,
|
||||||
|
generateResourceName,
|
||||||
|
generateRoleName,
|
||||||
|
generateScopeName,
|
||||||
|
} from '#src/utils.js';
|
||||||
|
|
||||||
describe('consent api', () => {
|
describe('consent api', () => {
|
||||||
const applications = new Map<string, Application>();
|
const applications = new Map<string, Application>();
|
||||||
|
@ -126,6 +135,63 @@ describe('consent api', () => {
|
||||||
await deleteUser(user.id);
|
await deleteUser(user.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('get consent info with organization resource scopes', async () => {
|
||||||
|
const application = applications.get(thirdPartyApplicationName);
|
||||||
|
assert(application, new Error('application.not_found'));
|
||||||
|
|
||||||
|
const resource = await createResource(generateResourceName(), generateResourceIndicator());
|
||||||
|
const scope = await createScope(resource.id, generateScopeName());
|
||||||
|
const scope2 = await createScope(resource.id, generateScopeName());
|
||||||
|
const roleApi = new OrganizationRoleApiTest();
|
||||||
|
const role = await roleApi.create({
|
||||||
|
name: generateRoleName(),
|
||||||
|
resourceScopeIds: [scope.id],
|
||||||
|
});
|
||||||
|
const organizationApi = new OrganizationApiTest();
|
||||||
|
const organization = await organizationApi.create({ name: 'test_org' });
|
||||||
|
const { userProfile, user } = await generateNewUser({ username: true, password: true });
|
||||||
|
await organizationApi.addUsers(organization.id, [user.id]);
|
||||||
|
await organizationApi.addUserRoles(organization.id, user.id, [role.id]);
|
||||||
|
|
||||||
|
await assignUserConsentScopes(application.id, {
|
||||||
|
organizationResourceScopes: [scope.id],
|
||||||
|
userScopes: [UserScope.Organizations],
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = await initClient(
|
||||||
|
{
|
||||||
|
appId: application.id,
|
||||||
|
appSecret: application.secret,
|
||||||
|
scopes: [UserScope.Organizations, UserScope.Profile, scope.name, scope2.name],
|
||||||
|
resources: [resource.indicator],
|
||||||
|
},
|
||||||
|
redirectUri
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.successSend(putInteraction, {
|
||||||
|
event: InteractionEvent.SignIn,
|
||||||
|
identifier: {
|
||||||
|
username: userProfile.username,
|
||||||
|
password: userProfile.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { redirectTo } = await client.submitInteraction();
|
||||||
|
|
||||||
|
await client.processSession(redirectTo, false);
|
||||||
|
|
||||||
|
const result = await client.send(getConsentInfo);
|
||||||
|
|
||||||
|
expect(result.missingResourceScopes).toHaveLength(0);
|
||||||
|
// Only scope1, scope2 is removed
|
||||||
|
expect(result.organizations?.[0]?.missingResourceScopes).toHaveLength(1);
|
||||||
|
|
||||||
|
await roleApi.cleanUp();
|
||||||
|
await organizationApi.cleanUp();
|
||||||
|
await deleteResource(resource.id);
|
||||||
|
await deleteUser(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
for (const application of applications.values()) {
|
for (const application of applications.values()) {
|
||||||
void deleteApplication(application.id);
|
void deleteApplication(application.id);
|
||||||
|
|
|
@ -37,14 +37,6 @@ export const applicationSignInExperienceGuard = ApplicationSignInExperiences.gua
|
||||||
termsOfUseUrl: true,
|
termsOfUseUrl: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the public organization info that can be exposed to the public. e.g. on the user consent page.
|
|
||||||
*/
|
|
||||||
export const publicOrganizationGuard = Organizations.guard.pick({
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const missingResourceScopesGuard = z.object({
|
export const missingResourceScopesGuard = z.object({
|
||||||
// The original resource id has a maximum length of 21 restriction. We need to make it compatible with the logto reserved organization name.
|
// The original resource id has a maximum length of 21 restriction. We need to make it compatible with the logto reserved organization name.
|
||||||
// use string here, as we do not care about the resource id length here.
|
// use string here, as we do not care about the resource id length here.
|
||||||
|
@ -57,6 +49,20 @@ export const missingResourceScopesGuard = z.object({
|
||||||
*/
|
*/
|
||||||
export type MissingResourceScopes = z.infer<typeof missingResourceScopesGuard>;
|
export type MissingResourceScopes = z.infer<typeof missingResourceScopesGuard>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the public organization info that can be exposed to the public. e.g. on the user consent page.
|
||||||
|
*/
|
||||||
|
export const publicOrganizationGuard = Organizations.guard
|
||||||
|
.pick({
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
missingResourceScopes: missingResourceScopesGuard.array().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PublicOrganization = z.infer<typeof publicOrganizationGuard>;
|
||||||
|
|
||||||
export const consentInfoResponseGuard = z.object({
|
export const consentInfoResponseGuard = z.object({
|
||||||
application: publicApplicationGuard.merge(applicationSignInExperienceGuard.partial()),
|
application: publicApplicationGuard.merge(applicationSignInExperienceGuard.partial()),
|
||||||
user: publicUserInfoGuard,
|
user: publicUserInfoGuard,
|
||||||
|
|
Loading…
Reference in a new issue