mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #3879 from logto-io/gao-feature-updates-for-openai-plugins
feat: updates for openai plugins
This commit is contained in:
commit
02eee1956f
41 changed files with 475 additions and 59 deletions
|
@ -1,5 +1,14 @@
|
|||
# Change Log
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d6720805: update default OpenAI concurrency to 1 for sync command
|
||||
- Updated dependencies [5d6720805]
|
||||
- @logto/phrases@1.3.0
|
||||
- @logto/schemas@1.4.0
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/cli",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"description": "Logto CLI.",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"homepage": "https://github.com/logto-io/logto#readme",
|
||||
|
@ -46,9 +46,9 @@
|
|||
"@logto/connector-kit": "workspace:^1.1.1",
|
||||
"@logto/core-kit": "workspace:^2.0.0",
|
||||
"@logto/language-kit": "workspace:^1.0.0",
|
||||
"@logto/phrases": "workspace:^1.2.0",
|
||||
"@logto/phrases": "workspace:^1.3.0",
|
||||
"@logto/phrases-ui": "workspace:^1.2.0",
|
||||
"@logto/schemas": "workspace:1.3.1",
|
||||
"@logto/schemas": "workspace:1.4.0",
|
||||
"@logto/shared": "workspace:^2.0.0",
|
||||
"@silverhand/essentials": "^2.5.0",
|
||||
"chalk": "^5.0.0",
|
||||
|
|
|
@ -13,7 +13,7 @@ const sync: CommandModule<{ path?: string }, { path?: string }> = {
|
|||
describe:
|
||||
'Translate all untranslated phrases using ChatGPT. Note the environment variable `OPENAI_API_KEY` is required to work.',
|
||||
handler: async ({ path: inputPath }) => {
|
||||
const queue = new PQueue({ concurrency: 5 });
|
||||
const queue = new PQueue({ concurrency: 1 });
|
||||
const instancePath = await inquireInstancePath(inputPath);
|
||||
|
||||
for (const languageTag of Object.keys(languages)) {
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
# Change Log
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5d6720805: add config `alwaysIssueRefreshToken` for web apps to unblock OAuth integrations that are not strictly conform OpenID Connect.
|
||||
|
||||
when it's enabled, Refresh Tokens will be always issued regardless if `prompt=consent` was present in the authorization request.
|
||||
|
||||
## 1.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/console",
|
||||
"version": "1.2.4",
|
||||
"version": "1.3.0",
|
||||
"description": "> TODO: description",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"homepage": "https://github.com/logto-io/logto#readme",
|
||||
|
@ -29,10 +29,10 @@
|
|||
"@logto/connector-kit": "workspace:^1.1.1",
|
||||
"@logto/core-kit": "workspace:^2.0.0",
|
||||
"@logto/language-kit": "workspace:^1.0.0",
|
||||
"@logto/phrases": "workspace:^1.2.0",
|
||||
"@logto/phrases": "workspace:^1.3.0",
|
||||
"@logto/phrases-ui": "workspace:^1.2.0",
|
||||
"@logto/react": "^2.0.0",
|
||||
"@logto/schemas": "workspace:^1.3.0",
|
||||
"@logto/schemas": "workspace:^1.4.0",
|
||||
"@logto/shared": "workspace:^2.0.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@parcel/compressor-brotli": "2.8.3",
|
||||
|
|
|
@ -64,6 +64,14 @@ function AdvancedSettings({ applicationType, oidcConfig }: Props) {
|
|||
variant="border"
|
||||
/>
|
||||
</FormField>
|
||||
{[ApplicationType.Traditional, ApplicationType.SPA].includes(applicationType) && (
|
||||
<FormField title="application_details.always_issue_refresh_token">
|
||||
<Switch
|
||||
label={t('application_details.always_issue_refresh_token_label')}
|
||||
{...register('customClientMetadata.alwaysIssueRefreshToken')}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
{applicationType === ApplicationType.MachineToMachine && (
|
||||
<FormField title="application_details.enable_admin_access">
|
||||
<Switch
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
# Change Log
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 9a3aa3aae: Automatically sync the trusted social email and phone info to the new registered user profile
|
||||
- 5d6720805: add config `alwaysIssueRefreshToken` for web apps to unblock OAuth integrations that are not strictly conform OpenID Connect.
|
||||
|
||||
when it's enabled, Refresh Tokens will be always issued regardless if `prompt=consent` was present in the authorization request.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d6720805: parse requests with `application/json` content-type for `/oidc` APIs to increase compatibility
|
||||
- Updated dependencies [5d6720805]
|
||||
- Updated dependencies [5d6720805]
|
||||
- @logto/cli@1.4.0
|
||||
- @logto/console@1.3.0
|
||||
- @logto/phrases@1.3.0
|
||||
- @logto/schemas@1.4.0
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/core",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"description": "The open source identity solution.",
|
||||
"main": "build/index.js",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
|
@ -29,15 +29,15 @@
|
|||
"@azure/storage-blob": "^12.13.0",
|
||||
"@koa/cors": "^4.0.0",
|
||||
"@logto/app-insights": "workspace:^1.2.0",
|
||||
"@logto/cli": "workspace:^1.3.1",
|
||||
"@logto/cli": "workspace:^1.4.0",
|
||||
"@logto/connector-kit": "workspace:^1.1.1",
|
||||
"@logto/console": "workspace:*",
|
||||
"@logto/core-kit": "workspace:^2.0.0",
|
||||
"@logto/demo-app": "workspace:*",
|
||||
"@logto/language-kit": "workspace:^1.0.0",
|
||||
"@logto/phrases": "workspace:^1.2.0",
|
||||
"@logto/phrases": "workspace:^1.3.0",
|
||||
"@logto/phrases-ui": "workspace:^1.2.0",
|
||||
"@logto/schemas": "workspace:^1.3.1",
|
||||
"@logto/schemas": "workspace:^1.4.0",
|
||||
"@logto/shared": "workspace:^2.0.0",
|
||||
"@logto/ui": "workspace:*",
|
||||
"@silverhand/essentials": "^2.5.0",
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import i18next from 'i18next';
|
||||
import koaBody from 'koa-body';
|
||||
import Provider, { errors, type ResourceServer } from 'oidc-provider';
|
||||
import snakecaseKeys from 'snakecase-keys';
|
||||
|
||||
|
@ -151,6 +152,16 @@ export default function initOidc(
|
|||
},
|
||||
},
|
||||
},
|
||||
issueRefreshToken: (_, client, code) => {
|
||||
if (!client.grantTypeAllowed('refresh_token')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
code.scopes.has('offline_access') ||
|
||||
(client.applicationType === 'web' && Boolean(client.metadata().alwaysIssueRefreshToken))
|
||||
);
|
||||
},
|
||||
interactions: {
|
||||
url: (ctx, { params: { client_id: appId }, prompt }) => {
|
||||
const isDemoApp = appId === demoAppApplicationId;
|
||||
|
@ -256,7 +267,7 @@ export default function initOidc(
|
|||
},
|
||||
pkce: {
|
||||
required: (ctx, client) => {
|
||||
return client.tokenEndpointAuthMethod !== 'client_secret_basic';
|
||||
return client.clientAuthMethod !== 'client_secret_basic';
|
||||
},
|
||||
methods: ['S256'],
|
||||
},
|
||||
|
@ -266,6 +277,30 @@ export default function initOidc(
|
|||
|
||||
// Provide audit log context for event listeners
|
||||
oidc.use(koaAuditLog(queries));
|
||||
/**
|
||||
* Create a middleware function that transpile requests with content type `application/json`
|
||||
* since `oidc-provider` only accepts `application/x-www-form-urlencoded` for most of routes.
|
||||
*
|
||||
* Other parsers are explicitly disabled to keep it neat.
|
||||
*/
|
||||
oidc.use(koaBody({ urlencoded: false, text: false }));
|
||||
/**
|
||||
* `oidc-provider` [strictly checks](https://github.com/panva/node-oidc-provider/blob/6a0bcbcd35ed3e6179e81f0ab97a45f5e4e58f48/lib/shared/selective_body.js#L11)
|
||||
* the `content-type` header for further processing.
|
||||
*
|
||||
* It will [directly use the `ctx.req.body` for parsing](https://github.com/panva/node-oidc-provider/blob/6a0bcbcd35ed3e6179e81f0ab97a45f5e4e58f48/lib/shared/selective_body.js#L39)
|
||||
* so there's no need to change the raw request body as we uses `koaBody()` above.
|
||||
*
|
||||
* However, this is not recommended for other routes rather since it causes a header-body format mismatch.
|
||||
*/
|
||||
oidc.use(async (ctx, next) => {
|
||||
// WARNING: [Registration actions](https://github.com/panva/node-oidc-provider/blob/6a0bcbcd35ed3e6179e81f0ab97a45f5e4e58f48/lib/actions/registration.js#L4) are using
|
||||
// 'application/json' for body parsing. Update relatively when we enable that feature.
|
||||
if (ctx.headers['content-type'] === 'application/json') {
|
||||
ctx.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
return next();
|
||||
});
|
||||
oidc.use(koaBodyEtag());
|
||||
|
||||
return oidc;
|
||||
|
|
|
@ -73,7 +73,13 @@ describe('submit action', () => {
|
|||
connectorId: 'logto',
|
||||
};
|
||||
|
||||
const userInfo = { id: 'foo', name: 'foo_social', avatar: 'avatar' };
|
||||
const userInfo = {
|
||||
id: 'foo',
|
||||
name: 'foo_social',
|
||||
avatar: 'avatar',
|
||||
email: 'email@socail.com',
|
||||
phone: '123123',
|
||||
};
|
||||
|
||||
const identifiers: Identifier[] = [
|
||||
{
|
||||
|
@ -127,6 +133,37 @@ describe('submit action', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('register new social user', async () => {
|
||||
const interaction: VerifiedRegisterInteractionResult = {
|
||||
event: InteractionEvent.Register,
|
||||
profile: { connectorId: 'logto', username: 'username' },
|
||||
identifiers,
|
||||
};
|
||||
|
||||
await submitInteraction(interaction, ctx, tenant);
|
||||
|
||||
expect(generateUserId).toBeCalled();
|
||||
expect(hasActiveUsers).not.toBeCalled();
|
||||
expect(encryptUserPassword).not.toBeCalled();
|
||||
expect(getLogtoConnectorById).toBeCalledWith('logto');
|
||||
|
||||
expect(insertUser).toBeCalledWith(
|
||||
{
|
||||
id: 'uid',
|
||||
username: 'username',
|
||||
identities: {
|
||||
logto: { userId: userInfo.id, details: userInfo },
|
||||
},
|
||||
name: userInfo.name,
|
||||
avatar: userInfo.avatar,
|
||||
primaryEmail: userInfo.email,
|
||||
primaryPhone: userInfo.phone,
|
||||
lastSignInAt: now,
|
||||
},
|
||||
['user']
|
||||
);
|
||||
});
|
||||
|
||||
it('admin user register', async () => {
|
||||
hasActiveUsers.mockResolvedValueOnce(false);
|
||||
const adminConsoleCtx = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, CoreEvent, getEventName } from '@logto/app-insights/custom-event';
|
||||
import { appInsights } from '@logto/app-insights/node';
|
||||
import type { User, Profile } from '@logto/schemas';
|
||||
import type { User, Profile, CreateUser } from '@logto/schemas';
|
||||
import {
|
||||
AdminTenantRole,
|
||||
SignInMode,
|
||||
|
@ -10,6 +10,7 @@ import {
|
|||
InteractionEvent,
|
||||
adminConsoleApplicationId,
|
||||
} from '@logto/schemas';
|
||||
import { type OmitAutoSetFields } from '@logto/shared';
|
||||
import { conditional, conditionalArray } from '@silverhand/essentials';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
|
@ -46,7 +47,6 @@ const getNewSocialProfile = async (
|
|||
}
|
||||
) => {
|
||||
// TODO: @simeng refactor me. This step should be verified by the previous profile verification cycle Already.
|
||||
// Should pickup the verified social user info result automatically
|
||||
const socialIdentifier = identifiers.find((identifier) => identifier.connectorId === connectorId);
|
||||
|
||||
if (!socialIdentifier) {
|
||||
|
@ -59,26 +59,43 @@ const getNewSocialProfile = async (
|
|||
} = await getLogtoConnectorById(connectorId);
|
||||
|
||||
const { userInfo } = socialIdentifier;
|
||||
const { name, avatar, id } = userInfo;
|
||||
const { name, avatar, id, email, phone } = userInfo;
|
||||
|
||||
// Update the user name and avatar if the connector has syncProfile enabled or is new registered user
|
||||
const profileUpdate = conditional(
|
||||
(syncProfile || !user) && {
|
||||
const identities = { ...user?.identities, [target]: { userId: id, details: userInfo } };
|
||||
|
||||
// Sync the name, avatar, email and phone for new user
|
||||
if (!user) {
|
||||
return {
|
||||
identities,
|
||||
...conditional(name && { name }),
|
||||
...conditional(avatar && { avatar }),
|
||||
}
|
||||
);
|
||||
...conditional(email && { primaryEmail: email }),
|
||||
...conditional(phone && { primaryPhone: phone }),
|
||||
};
|
||||
}
|
||||
|
||||
// Sync the user name and avatar if the connector has syncProfile enabled
|
||||
return {
|
||||
identities: { ...user?.identities, [target]: { userId: id, details: userInfo } },
|
||||
...profileUpdate,
|
||||
identities,
|
||||
...conditional(
|
||||
syncProfile && {
|
||||
...conditional(name && { name }),
|
||||
...conditional(avatar && { avatar }),
|
||||
}
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const getSyncedSocialUserProfile = async (
|
||||
const getLatestUserProfileFromSocial = async (
|
||||
{ getLogtoConnectorById }: ConnectorLibrary,
|
||||
socialIdentifier: SocialIdentifier
|
||||
authIdentifiers: Identifier[]
|
||||
) => {
|
||||
const socialIdentifier = filterSocialIdentifiers(authIdentifiers).slice(-1)[0];
|
||||
|
||||
if (!socialIdentifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
userInfo: { name, avatar },
|
||||
connectorId,
|
||||
|
@ -117,11 +134,11 @@ const parseNewUserProfile = async (
|
|||
]);
|
||||
|
||||
return {
|
||||
...socialProfile, // SocialProfile should be applied first
|
||||
...passwordProfile,
|
||||
...conditional(phone && { primaryPhone: phone }),
|
||||
...conditional(username && { username }),
|
||||
...conditional(email && { primaryEmail: email }),
|
||||
...passwordProfile,
|
||||
...socialProfile,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -129,17 +146,15 @@ const parseUserProfile = async (
|
|||
connectorLibrary: ConnectorLibrary,
|
||||
{ profile, identifiers }: VerifiedSignInInteractionResult | VerifiedRegisterInteractionResult,
|
||||
user?: User
|
||||
) => {
|
||||
): Promise<Omit<OmitAutoSetFields<CreateUser>, 'id'>> => {
|
||||
const { authIdentifiers, profileIdentifiers } = categorizeIdentifiers(identifiers ?? [], profile);
|
||||
|
||||
const newUserProfile =
|
||||
profile && (await parseNewUserProfile(connectorLibrary, profile, profileIdentifiers, user));
|
||||
|
||||
// Sync the last social profile
|
||||
const socialIdentifier = filterSocialIdentifiers(authIdentifiers).slice(-1)[0];
|
||||
|
||||
// Sync from the latest social identity profile for existing users
|
||||
const syncedSocialUserProfile =
|
||||
socialIdentifier && (await getSyncedSocialUserProfile(connectorLibrary, socialIdentifier));
|
||||
user && (await getLatestUserProfileFromSocial(connectorLibrary, authIdentifiers));
|
||||
|
||||
return {
|
||||
...syncedSocialUserProfile,
|
||||
|
@ -151,7 +166,7 @@ const parseUserProfile = async (
|
|||
export default async function submitInteraction(
|
||||
interaction: VerifiedInteractionResult,
|
||||
ctx: WithInteractionDetailsContext,
|
||||
{ provider, libraries, connectors, queries, id: tenantId }: TenantContext,
|
||||
{ provider, libraries, connectors, queries }: TenantContext,
|
||||
log?: LogEntry
|
||||
) {
|
||||
const { hasActiveUsers, findUserById, updateUserById } = queries.users;
|
||||
|
@ -164,7 +179,7 @@ export default async function submitInteraction(
|
|||
|
||||
if (event === InteractionEvent.Register) {
|
||||
const id = await generateUserId();
|
||||
const upsertProfile = await parseUserProfile(connectors, interaction);
|
||||
const userProfile = await parseUserProfile(connectors, interaction);
|
||||
|
||||
const { client_id } = ctx.interactionDetails.params;
|
||||
|
||||
|
@ -178,7 +193,7 @@ export default async function submitInteraction(
|
|||
await insertUser(
|
||||
{
|
||||
id,
|
||||
...upsertProfile,
|
||||
...userProfile,
|
||||
},
|
||||
conditionalArray<string>(
|
||||
isInAdminTenant && AdminTenantRole.User,
|
||||
|
@ -208,9 +223,9 @@ export default async function submitInteraction(
|
|||
|
||||
if (event === InteractionEvent.SignIn) {
|
||||
const user = await findUserById(accountId);
|
||||
const upsertProfile = await parseUserProfile(connectors, interaction, user);
|
||||
const updateUserProfile = await parseUserProfile(connectors, interaction, user);
|
||||
|
||||
await updateUserById(accountId, upsertProfile);
|
||||
await updateUserById(accountId, updateUserProfile);
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId } });
|
||||
|
||||
appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.SignIn) });
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
# Change Log
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5d6720805]
|
||||
- @logto/cli@1.4.0
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/create",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
|
@ -15,6 +15,6 @@
|
|||
"node": "^18.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/cli": "workspace:^1.3.1"
|
||||
"@logto/cli": "workspace:^1.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Change Log
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 9a3aa3aae: Automatically sync the trusted social email and phone info to the new registered user profile
|
||||
|
||||
## 1.1.0
|
||||
|
||||
## 1.0.3
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/integration-tests",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "Integration tests for Logto.",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"license": "MPL-2.0",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"@logto/connector-kit": "workspace:^1.1.0",
|
||||
"@logto/js": "^2.0.1",
|
||||
"@logto/node": "^2.0.0",
|
||||
"@logto/schemas": "workspace:^1.1.0",
|
||||
"@logto/schemas": "workspace:^1.4.0",
|
||||
"@silverhand/eslint-config": "3.0.1",
|
||||
"@silverhand/essentials": "^2.5.0",
|
||||
"@silverhand/ts-config": "3.0.0",
|
||||
|
|
|
@ -7,12 +7,17 @@ import type {
|
|||
|
||||
import { authedAdminApi } from './api.js';
|
||||
|
||||
export const createApplication = async (name: string, type: ApplicationType) =>
|
||||
export const createApplication = async (
|
||||
name: string,
|
||||
type: ApplicationType,
|
||||
rest?: Partial<CreateApplication>
|
||||
) =>
|
||||
authedAdminApi
|
||||
.post('applications', {
|
||||
json: {
|
||||
name,
|
||||
type,
|
||||
...rest,
|
||||
},
|
||||
})
|
||||
.json<Application>();
|
||||
|
|
|
@ -4,6 +4,7 @@ import { mockSocialConnectorId } from '#src/__mocks__/connectors-mock.js';
|
|||
import {
|
||||
createSocialAuthorizationUri,
|
||||
putInteraction,
|
||||
getUser,
|
||||
deleteUser,
|
||||
putInteractionEvent,
|
||||
patchInteractionIdentifiers,
|
||||
|
@ -97,6 +98,72 @@ describe('Social Identifier Interactions', () => {
|
|||
await logoutClient(client);
|
||||
await deleteUser(id);
|
||||
});
|
||||
|
||||
it('register with social and synced email', async () => {
|
||||
const client = await initClient();
|
||||
const socialEmail = generateEmail();
|
||||
|
||||
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
|
||||
|
||||
await client.successSend(putInteraction, {
|
||||
event: InteractionEvent.SignIn,
|
||||
});
|
||||
|
||||
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
|
||||
|
||||
await client.successSend(patchInteractionIdentifiers, {
|
||||
connectorId,
|
||||
connectorData: { state, redirectUri, code, userId: socialUserId, email: socialEmail },
|
||||
});
|
||||
|
||||
await expectRejects(client.submitInteraction(), 'user.identity_not_exist');
|
||||
|
||||
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
|
||||
await client.successSend(putInteractionProfile, { connectorId });
|
||||
|
||||
const { redirectTo } = await client.submitInteraction();
|
||||
|
||||
const uid = await processSession(client, redirectTo);
|
||||
|
||||
const { primaryEmail } = await getUser(uid);
|
||||
expect(primaryEmail).toBe(socialEmail);
|
||||
|
||||
await logoutClient(client);
|
||||
await deleteUser(uid);
|
||||
});
|
||||
|
||||
it('register with social and synced phone', async () => {
|
||||
const client = await initClient();
|
||||
const socialPhone = generatePhone();
|
||||
|
||||
const connectorId = connectorIdMap.get(mockSocialConnectorId) ?? '';
|
||||
|
||||
await client.successSend(putInteraction, {
|
||||
event: InteractionEvent.SignIn,
|
||||
});
|
||||
|
||||
await client.successSend(createSocialAuthorizationUri, { state, redirectUri, connectorId });
|
||||
|
||||
await client.successSend(patchInteractionIdentifiers, {
|
||||
connectorId,
|
||||
connectorData: { state, redirectUri, code, userId: socialUserId, phone: socialPhone },
|
||||
});
|
||||
|
||||
await expectRejects(client.submitInteraction(), 'user.identity_not_exist');
|
||||
|
||||
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
|
||||
await client.successSend(putInteractionProfile, { connectorId });
|
||||
|
||||
const { redirectTo } = await client.submitInteraction();
|
||||
|
||||
const uid = await processSession(client, redirectTo);
|
||||
|
||||
const { primaryPhone } = await getUser(uid);
|
||||
expect(primaryPhone).toBe(socialPhone);
|
||||
|
||||
await logoutClient(client);
|
||||
await deleteUser(uid);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind with existing email account', () => {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { Prompt } from '@logto/js';
|
||||
import { ApplicationType, InteractionEvent } from '@logto/schemas';
|
||||
|
||||
import { createApplication, deleteApplication, putInteraction } from '#src/api/index.js';
|
||||
import MockClient from '#src/client/index.js';
|
||||
import { demoAppRedirectUri } from '#src/constants.js';
|
||||
import { processSession } from '#src/helpers/client.js';
|
||||
import { createUserByAdmin } from '#src/helpers/index.js';
|
||||
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
|
||||
import { generateUsername, generatePassword } from '#src/utils.js';
|
||||
|
||||
describe('always issue Refresh Token config', () => {
|
||||
const username = generateUsername();
|
||||
const password = generatePassword();
|
||||
|
||||
const validateRefreshToken = async (appId: string, redirectUri: string, expectToken: boolean) => {
|
||||
const client = new MockClient({
|
||||
appId,
|
||||
prompt: Prompt.Login,
|
||||
});
|
||||
await client.initSession(redirectUri);
|
||||
await client.successSend(putInteraction, {
|
||||
event: InteractionEvent.SignIn,
|
||||
identifier: { username, password },
|
||||
});
|
||||
const { redirectTo } = await client.submitInteraction();
|
||||
await processSession(client, redirectTo);
|
||||
|
||||
if (expectToken) {
|
||||
expect(await client.getRefreshToken()).not.toBeNull();
|
||||
} else {
|
||||
expect(await client.getRefreshToken()).toBeNull();
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await createUserByAdmin(username, password);
|
||||
await enableAllPasswordSignInMethods();
|
||||
});
|
||||
|
||||
it('can sign in and fetch Refresh Token without `prompt=consent` when always issue Refresh Token is set', async () => {
|
||||
const app = await createApplication('Integration test app', ApplicationType.SPA, {
|
||||
oidcClientMetadata: { redirectUris: [demoAppRedirectUri], postLogoutRedirectUris: [] },
|
||||
customClientMetadata: { alwaysIssueRefreshToken: true },
|
||||
});
|
||||
await validateRefreshToken(app.id, demoAppRedirectUri, true);
|
||||
await deleteApplication(app.id);
|
||||
});
|
||||
|
||||
it('cannot fetch Refresh Token if alwaysIssueRefreshToken is false and prompt is not consent', async () => {
|
||||
const app = await createApplication('Integration test app', ApplicationType.SPA, {
|
||||
oidcClientMetadata: { redirectUris: [demoAppRedirectUri], postLogoutRedirectUris: [] },
|
||||
customClientMetadata: { alwaysIssueRefreshToken: false },
|
||||
});
|
||||
await validateRefreshToken(app.id, demoAppRedirectUri, false);
|
||||
await deleteApplication(app.id);
|
||||
});
|
||||
|
||||
it('cannot fetch Refresh Token for non-web apps', async () => {
|
||||
const redirectUri = 'io.logto://callback';
|
||||
const app = await createApplication('Integration test app', ApplicationType.Native, {
|
||||
oidcClientMetadata: { redirectUris: [redirectUri], postLogoutRedirectUris: [] },
|
||||
customClientMetadata: { alwaysIssueRefreshToken: true },
|
||||
});
|
||||
await validateRefreshToken(app.id, redirectUri, false);
|
||||
await deleteApplication(app.id);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { demoAppApplicationId } from '@logto/schemas';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { HTTPError, type Headers, got } from 'got';
|
||||
|
||||
import { logtoUrl } from '#src/constants.js';
|
||||
|
||||
describe('content-type: application/json compatibility', () => {
|
||||
const api = got.extend({
|
||||
prefixUrl: new URL('/oidc', logtoUrl),
|
||||
});
|
||||
|
||||
const expectErrorMessageForPayload = async (
|
||||
payload: Record<string, unknown>,
|
||||
errorMessage: string,
|
||||
headers: Headers = {}
|
||||
) => {
|
||||
return trySafe(
|
||||
api.post('token', {
|
||||
headers,
|
||||
json: payload,
|
||||
}),
|
||||
(error) => {
|
||||
if (!(error instanceof HTTPError)) {
|
||||
throw new TypeError('Error is not a HTTPError instance.');
|
||||
}
|
||||
expect(JSON.parse(String(error.response.body))).toHaveProperty(
|
||||
'error_description',
|
||||
errorMessage
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
it('recognizes `application/json` content-type in OIDC token endpoints', async () => {
|
||||
await Promise.all([
|
||||
expectErrorMessageForPayload(
|
||||
{ client_id: demoAppApplicationId },
|
||||
"missing required parameter 'grant_type'"
|
||||
),
|
||||
expectErrorMessageForPayload(
|
||||
{ client_id: demoAppApplicationId, grant_type: 'refresh_token' },
|
||||
"missing required parameter 'refresh_token'"
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not recognize `application/json1` content-type', async () => {
|
||||
await expectErrorMessageForPayload(
|
||||
{ client_id: demoAppApplicationId },
|
||||
'only application/x-www-form-urlencoded content-type bodies are supported on POST /token',
|
||||
{ 'content-type': 'application/json1' }
|
||||
);
|
||||
});
|
||||
});
|
|
@ -26,7 +26,7 @@ describe('get access token', () => {
|
|||
await enableAllPasswordSignInMethods();
|
||||
});
|
||||
|
||||
it('sign-in and getAccessToken with admin user', async () => {
|
||||
it('can sign in and getAccessToken with admin user', async () => {
|
||||
const client = new MockClient({
|
||||
resources: [defaultManagementApi.resource.indicator],
|
||||
scopes: [defaultManagementApi.scope.name],
|
||||
|
@ -49,7 +49,7 @@ describe('get access token', () => {
|
|||
void expect(client.getAccessToken('api.foo.com')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('sign-in and getAccessToken with guest user', async () => {
|
||||
it('can sign in and getAccessToken with guest user', async () => {
|
||||
const client = new MockClient({
|
||||
resources: [defaultManagementApi.resource.indicator],
|
||||
scopes: [defaultManagementApi.scope.name],
|
||||
|
@ -69,7 +69,7 @@ describe('get access token', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('sign-in and get multiple Access Token by the same Refresh Token within refreshTokenReuseInterval', async () => {
|
||||
it('can sign in and get multiple Access Tokens by the same Refresh Token within refreshTokenReuseInterval', async () => {
|
||||
const client = new MockClient({ resources: [defaultManagementApi.resource.indicator] });
|
||||
|
||||
await client.initSession();
|
|
@ -1,5 +1,13 @@
|
|||
# Change Log
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5d6720805: add config `alwaysIssueRefreshToken` for web apps to unblock OAuth integrations that are not strictly conform OpenID Connect.
|
||||
|
||||
when it's enabled, Refresh Tokens will be always issued regardless if `prompt=consent` was present in the authorization request.
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/phrases",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"description": "Logto shared phrases (i18n).",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"homepage": "https://github.com/logto-io/logto#readme",
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Admin-Zugang aktivieren',
|
||||
enable_admin_access_label:
|
||||
'Zugang zur Management API aktivieren oder deaktivieren. Wenn aktiviert, können Access Tokens verwendet werden, um die Management API im Namen der Anwendung aufzurufen.',
|
||||
always_issue_refresh_token: 'Immer den Refresh Token ausgeben',
|
||||
always_issue_refresh_token_label:
|
||||
'Durch Aktivieren dieser Konfiguration kann Logto immer Refresh Tokens ausgeben, unabhängig davon, ob in der Authentifizierungsanforderung "prompt=consent" angegeben ist. Diese Praxis wird jedoch nur dann empfohlen, wenn es notwendig ist, da sie nicht mit OpenID Connect kompatibel ist und möglicherweise Probleme verursacht.',
|
||||
delete_description:
|
||||
'Diese Aktion kann nicht rückgängig gemacht werden. Die Anwendung wird permanent gelöscht. Bitte gib den Anwendungsnamen <span>{{name}}</span> zur Bestätigung ein.',
|
||||
enter_your_application_name: 'Gib einen Anwendungsnamen ein',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Enable admin access',
|
||||
enable_admin_access_label:
|
||||
'Enable or disable the access to Management API. Once enabled, you can use access tokens to call Management API on behalf on this application.',
|
||||
always_issue_refresh_token: 'Always issue Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'Enabling this configuration will allow Logto to always issue Refresh Tokens, regardless of whether `prompt=consent` is presented in the authentication request. However, this practice is discouraged unless necessary, as it is not compatible with OpenID Connect and may potentially cause issues.',
|
||||
delete_description:
|
||||
'This action cannot be undone. It will permanently delete the application. Please enter the application name <span>{{name}}</span> to confirm.',
|
||||
enter_your_application_name: 'Enter your application name',
|
||||
|
|
|
@ -41,6 +41,9 @@ const detalles_aplicacion = {
|
|||
enable_admin_access: 'Habilitar acceso de administrador',
|
||||
enable_admin_access_label:
|
||||
'Habilita o deshabilita el acceso a la API de Gestión. Una vez habilitado, puedes utilizar tokens de acceso para llamar a la API de Gestión en nombre de esta aplicación.',
|
||||
always_issue_refresh_token: 'Siempre emitir Token de Refresco',
|
||||
always_issue_refresh_token_label:
|
||||
'Al habilitar esta configuración, Logto siempre emitirá Tokens de Refresco, independientemente de si se presenta o no “prompt=consent” en la solicitud de autenticación. Sin embargo, esta práctica no está recomendada a menos que sea necesario, ya que no es compatible con OpenID Connect y puede causar problemas potenciales.',
|
||||
delete_description:
|
||||
'Esta acción no se puede deshacer. Eliminará permanentemente la aplicación. Ingresa el nombre de la aplicación <span>{{name}}</span> para confirmar.',
|
||||
enter_your_application_name: 'Ingresa el nombre de tu aplicación',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: "Activer l'accès administrateur",
|
||||
enable_admin_access_label:
|
||||
"Activer ou désactiver l'accès à l'API de gestion. Une fois activé, vous pouvez utiliser des jetons d'accès pour appeler l'API de gestion au nom de cette application.",
|
||||
always_issue_refresh_token: 'Toujours émettre le Refresh Token.',
|
||||
always_issue_refresh_token_label:
|
||||
"En activant cette configuration, Logto pourra toujours émettre des Refresh Tokens, indépendamment de la présentation ou non de la demande d'authentification `prompt=consent`. Cependant, cette pratique est découragée sauf si nécessaire, car elle n'est pas compatible avec OpenID Connect et peut potentiellement causer des problèmes.",
|
||||
delete_description:
|
||||
"Cette action ne peut être annulée. Elle supprimera définitivement l'application. Veuillez entrer le nom de l'application <span>{{nom}}</span> pour confirmer.",
|
||||
enter_your_application_name: 'Entrez le nom de votre application',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: "Abilita l'accesso amministratore",
|
||||
enable_admin_access_label:
|
||||
"Abilita o disabilita l'accesso all'API di gestione. Una volta abilitato, puoi utilizzare i token di accesso per chiamare l'API di gestione a nome di questa applicazione.",
|
||||
always_issue_refresh_token: 'Rilascia sempre il token di aggiornamento',
|
||||
always_issue_refresh_token_label:
|
||||
'Abilitando questa configurazione, Logto potrà rilasciare sempre token di aggiornamento, anche se `prompt=consent` non viene presentata nella richiesta di autenticazione. Tuttavia, questa pratica è scoraggiata a meno che non sia necessaria, in quanto non è compatibile con OpenID Connect e potrebbe potenzialmente causare problemi.',
|
||||
delete_description:
|
||||
"Questa azione non può essere annullata. Eliminerà definitivamente l'applicazione. Inserisci il nome dell'applicazione <span>{{name}}</span> per confermare.",
|
||||
enter_your_application_name: 'Inserisci il nome della tua applicazione',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: '管理者アクセスを有効にする',
|
||||
enable_admin_access_label:
|
||||
'管理APIへのアクセスを有効または無効にします。有効にすると、アクセストークンを使用してこのアプリケーションを代表して管理APIを呼び出すことができます。',
|
||||
always_issue_refresh_token: '常にRefresh Tokenを発行する',
|
||||
always_issue_refresh_token_label:
|
||||
'この設定を有効にすると、Logtoは、認証要求に「prompt = consent」が提示されたかどうかにかかわらず、常にRefresh Tokenを発行することができます。ただし、OpenID Connectと互換性がないため、必要でない限りこのプラクティスは推奨されず、問題が発生する可能性があります。',
|
||||
delete_description:
|
||||
'この操作は元に戻すことはできません。アプリケーション名「<span>{{name}}</span>」を入力して確認してください。',
|
||||
enter_your_application_name: 'アプリケーション名を入力してください',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: '관리자 접근 활성화',
|
||||
enable_admin_access_label:
|
||||
'관리 API에 대한 접근을 활성화, 비활성화할 수 있어요. 활성화한다면, 이 어플리케이션에서 Access 토큰을 통해 관리 API를 사용할 수 있어요.',
|
||||
always_issue_refresh_token: '항상 Refresh Token을 발급하세요',
|
||||
always_issue_refresh_token_label:
|
||||
'다음 구성을 활성화하면 Logto가 인증 요청에 `prompt=consent`가 제시되었는지 여부와 상관없이 항상 Refresh Token을 발급할 수 있게 됩니다. 그러나 OpenID Connect와 호환되지 않으며 문제가 발생할 수 있으므로 필요하지 않은 경우에는 이러한 방법을 권장하지 않습니다. ',
|
||||
delete_description:
|
||||
'이 행동은 취소될 수 없어요. 어플리케이션을 영원히 삭제할 거에요. 삭제를 진행하기 위해 <span>{{name}}</span> 를 입력해주세요.',
|
||||
enter_your_application_name: '어플리케이션 이름을 입력해 주세요.',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Włącz dostęp administratora',
|
||||
enable_admin_access_label:
|
||||
'Włącz lub wyłącz dostęp do interfejsu API zarządzania. Po włączeniu możesz używać tokenów dostępu do wywoływania interfejsu API zarządzania w imieniu tej aplikacji.',
|
||||
always_issue_refresh_token: 'Zawsze wydawaj Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'Rozwiazanie tej konfiguracji pozwoli Logto zawsze wydawac cwiecze tokeny, bez wzgledu na to, czy w zadaniu autoryzacji zostal przedstawiony `prompt=consent`. Jednak ta praktyka jest odstraszana, chyba ze konieczne, jak nie jest w pelni kompatybilna z OpenID Connect i moze potencjalnie powodowac problemy.',
|
||||
delete_description:
|
||||
'Ta operacja nie może zostać cofnięta. Skutkuje ona trwałym usunięciem aplikacji. Aby potwierdzić, wpisz nazwę aplikacji <span>{{name}}</span>.',
|
||||
enter_your_application_name: 'Wpisz nazwę swojej aplikacji',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Ativar acesso de administrador',
|
||||
enable_admin_access_label:
|
||||
'Ative ou desative o acesso à API de gerenciamento. Uma vez ativado, você pode usar tokens de acesso para chamar a API de gerenciamento em nome deste aplicativo.',
|
||||
always_issue_refresh_token: 'Emitir sempre o token de refresh',
|
||||
always_issue_refresh_token_label:
|
||||
'Ativar esta configuração permitirá que a Logto emita sempre tokens de Refresh, independentemente de "prompt=consent" ser apresentado na solicitação de autenticação. No entanto, essa prática é desencorajada, a menos que seja necessária, pois não é compatível com o OpenID Connect e pode potencialmente causar problemas.',
|
||||
delete_description:
|
||||
'Essa ação não pode ser desfeita. Isso excluirá permanentemente o aplicativo. Insira o nome do aplicativo <span>{{name}}</span> para confirmar.',
|
||||
enter_your_application_name: 'Digite o nome do seu aplicativo',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Ativar o acesso de administrador',
|
||||
enable_admin_access_label:
|
||||
'Ativar ou desativar o acesso à API de gestão. Uma vez ativado, pode utilizar tokens de acesso para chamar a API de gestão em nome desta aplicação.',
|
||||
always_issue_refresh_token: 'Sempre emitir Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'Ao ativar essa configuração, a Logto sempre emitirá tokens de atualização, independentemente de `prompt=consent`ser apresentado na solicitação de autenticação. No entanto, essa prática é desencorajada, a menos que seja necessária, pois não é compatível com OpenID Connect e pode causar problemas.',
|
||||
delete_description:
|
||||
'Esta ação não pode ser revertida. Esta ação irá eliminar permanentemente a aplicação. Insira o nome da aplicação <span>{{name}}</span> para confirmar.',
|
||||
enter_your_application_name: 'Insira o nome da aplicação',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Включить доступ администратора',
|
||||
enable_admin_access_label:
|
||||
'Включить или отключить доступ к API управления. После включения вы можете использовать токены доступа для вызова API управления от имени этого приложения.',
|
||||
always_issue_refresh_token: 'Всегда выдавать Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'Включение этой настройки позволит Logto всегда выдавать Refresh Tokens, независимо от того, была ли в запросе на аутентификацию предложена команда `prompt=consent`. Однако данная практика не рекомендуется, если это необходимо, поскольку она несовместима с OpenID Connect и может вызвать проблемы.',
|
||||
delete_description:
|
||||
'Это действие нельзя отменить. Оно навсегда удалит приложение. Введите название приложения <span> {{name}} </span>, чтобы подтвердить.',
|
||||
enter_your_application_name: 'Введите название своего приложения',
|
||||
|
|
|
@ -41,6 +41,9 @@ const application_details = {
|
|||
enable_admin_access: 'Yönetici erişimini etkinleştir',
|
||||
enable_admin_access_label:
|
||||
"Yönetim API erişimine izin verme veya engelleme. Etkinleştirildikten sonra, bu uygulama adına yönetim API'sini çağırmak için erişim belirteçleri kullanabilirsiniz.",
|
||||
always_issue_refresh_token: 'Her zaman Refresh Token ver',
|
||||
always_issue_refresh_token_label:
|
||||
"Bu yapılandırmayı etkinleştirmek, Logto'nun OpenID Connect ile uyumlu olmayan ve olası sorunlara neden olabilecek her zaman Refresh Token çıkarmasına izin verir `prompt=consent` kimlik doğrulama isteğinin sunulup sunulmadığına bakılmaksızın. Ancak, bu uygulama yalnızca zorunlu olduğunda caydırılmayan bir uygulamadır.",
|
||||
delete_description:
|
||||
'Bu eylem geri alınamaz. Uygulama kalıcı olarak silinecektir. Lütfen onaylamak için uygulama adı <span>{{name}}</span> girin.',
|
||||
enter_your_application_name: 'Uygulama adı giriniz',
|
||||
|
|
|
@ -39,10 +39,13 @@ const application_details = {
|
|||
enable_admin_access: '启用管理访问',
|
||||
enable_admin_access_label:
|
||||
'启用或禁用对管理 API 的访问。启用后,你可以使用访问令牌代表该应用程序调用管理 API。',
|
||||
always_issue_refresh_token: '总是颁发 Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'启用此配置将允许 Logto 始终颁发 Refresh Token,无论身份验证请求中是否呈现 `prompt=consent`。 然而,除非必要,否则不推荐这样做,因为它与 OpenID Connect 不兼容,可能会导致问题。',
|
||||
delete_description: '本操作会永久性地删除该应用,且不可撤销。输入 <span>{{name}}</span> 确认。',
|
||||
enter_your_application_name: '输入你的应用名称',
|
||||
application_deleted: '应用 {{name}} 成功删除。',
|
||||
redirect_uri_required: '至少需要输入一个重定向 URL。',
|
||||
redirect_uri_required: '至少需要输入一个重定向 URI。',
|
||||
};
|
||||
|
||||
export default application_details;
|
||||
|
|
|
@ -39,6 +39,9 @@ const application_details = {
|
|||
enable_admin_access: '啟用管理訪問',
|
||||
enable_admin_access_label:
|
||||
'啟用或禁用對管理 API 的訪問。啟用後,你可以使用訪問權杖代表該應用程式調用管理 API。',
|
||||
always_issue_refresh_token: '始終發放 Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'啟用此配置將允許 Logto 始終發行 Refresh Token,無論是否在驗證請求中呈現 `prompt=consent`。但是,除非必要,否則不建議這樣做,因為它不兼容 OpenID Connect,可能會引起問題。',
|
||||
delete_description: '本操作會永久性地刪除該應用,且不可撤銷。輸入 <span>{{name}}</span> 確認。',
|
||||
enter_your_application_name: '輸入你的應用程式名稱',
|
||||
application_deleted: '應用 {{name}} 成功刪除。',
|
||||
|
|
|
@ -39,6 +39,9 @@ const application_details = {
|
|||
enable_admin_access: '啟用管理訪問',
|
||||
enable_admin_access_label:
|
||||
'啟用或禁用對管理 API 的訪問。啟用後,你可以使用訪問令牌代表該應用程式調用管理 API。',
|
||||
always_issue_refresh_token: '始終發放 Refresh Token',
|
||||
always_issue_refresh_token_label:
|
||||
'啟用此配置將使 Logto 無論在驗證請求中是否提供 prompt=consent,都能始終發放 Refresh Token。然而,除非必要,否則不鼓勵這種做法,因為它與 OpenID Connect 不相容並可能引起問題。',
|
||||
delete_description:
|
||||
'本操作會永久性地刪除該應用程式,且不可撤銷。輸入 <span>{{name}}</span> 確認。',
|
||||
enter_your_application_name: '輸入你的應用程式姓名',
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
# Change Log
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5d6720805: add config `alwaysIssueRefreshToken` for web apps to unblock OAuth integrations that are not strictly conform OpenID Connect.
|
||||
|
||||
when it's enabled, Refresh Tokens will be always issued regardless if `prompt=consent` was present in the authorization request.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5d6720805]
|
||||
- @logto/phrases@1.3.0
|
||||
|
||||
## 1.3.1
|
||||
|
||||
## 1.3.0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logto/schemas",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
|
@ -83,7 +83,7 @@
|
|||
"@logto/connector-kit": "workspace:^1.1.1",
|
||||
"@logto/core-kit": "workspace:^2.0.0",
|
||||
"@logto/language-kit": "workspace:^1.0.0",
|
||||
"@logto/phrases": "workspace:^1.2.0",
|
||||
"@logto/phrases": "workspace:^1.3.0",
|
||||
"@logto/phrases-ui": "workspace:^1.2.0",
|
||||
"@logto/shared": "workspace:^2.0.0",
|
||||
"@withtyped/server": "^0.9.0",
|
||||
|
|
|
@ -69,6 +69,14 @@ export enum CustomClientMetadataKey {
|
|||
IdTokenTtl = 'idTokenTtl',
|
||||
RefreshTokenTtl = 'refreshTokenTtl',
|
||||
TenantId = 'tenantId',
|
||||
/**
|
||||
* Enabling this configuration will allow Logto to always issue Refresh Tokens, regardless of whether `prompt=consent` is presented in the authentication request.
|
||||
*
|
||||
* It only works for web applications when the client allowed grant types includes `refresh_token`.
|
||||
*
|
||||
* This config is for the third-party integrations that do not strictly follow OpenID Connect standards due to some reasons (e.g. they only know OAuth, but requires a Refresh Token to be returned anyway).
|
||||
*/
|
||||
AlwaysIssueRefreshToken = 'alwaysIssueRefreshToken',
|
||||
}
|
||||
|
||||
export const customClientMetadataGuard = z.object({
|
||||
|
@ -76,8 +84,12 @@ export const customClientMetadataGuard = z.object({
|
|||
[CustomClientMetadataKey.IdTokenTtl]: z.number().optional(),
|
||||
[CustomClientMetadataKey.RefreshTokenTtl]: z.number().optional(),
|
||||
[CustomClientMetadataKey.TenantId]: z.string().optional(),
|
||||
[CustomClientMetadataKey.AlwaysIssueRefreshToken]: z.boolean().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @see {@link CustomClientMetadataKey} for key descriptions.
|
||||
*/
|
||||
export type CustomClientMetadata = z.infer<typeof customClientMetadataGuard>;
|
||||
|
||||
/* === Users === */
|
||||
|
|
|
@ -103,13 +103,13 @@ importers:
|
|||
specifier: workspace:^1.0.0
|
||||
version: link:../toolkit/language-kit
|
||||
'@logto/phrases':
|
||||
specifier: workspace:^1.2.0
|
||||
specifier: workspace:^1.3.0
|
||||
version: link:../phrases
|
||||
'@logto/phrases-ui':
|
||||
specifier: workspace:^1.2.0
|
||||
version: link:../phrases-ui
|
||||
'@logto/schemas':
|
||||
specifier: workspace:1.3.1
|
||||
specifier: workspace:1.4.0
|
||||
version: link:../schemas
|
||||
'@logto/shared':
|
||||
specifier: workspace:^2.0.0
|
||||
|
@ -2781,7 +2781,7 @@ importers:
|
|||
specifier: workspace:^1.0.0
|
||||
version: link:../toolkit/language-kit
|
||||
'@logto/phrases':
|
||||
specifier: workspace:^1.2.0
|
||||
specifier: workspace:^1.3.0
|
||||
version: link:../phrases
|
||||
'@logto/phrases-ui':
|
||||
specifier: workspace:^1.2.0
|
||||
|
@ -2790,7 +2790,7 @@ importers:
|
|||
specifier: ^2.0.0
|
||||
version: 2.0.0(react@18.2.0)
|
||||
'@logto/schemas':
|
||||
specifier: workspace:^1.3.0
|
||||
specifier: workspace:^1.4.0
|
||||
version: link:../schemas
|
||||
'@logto/shared':
|
||||
specifier: workspace:^2.0.0
|
||||
|
@ -3057,7 +3057,7 @@ importers:
|
|||
specifier: workspace:^1.2.0
|
||||
version: link:../app-insights
|
||||
'@logto/cli':
|
||||
specifier: workspace:^1.3.1
|
||||
specifier: workspace:^1.4.0
|
||||
version: link:../cli
|
||||
'@logto/connector-kit':
|
||||
specifier: workspace:^1.1.1
|
||||
|
@ -3075,13 +3075,13 @@ importers:
|
|||
specifier: workspace:^1.0.0
|
||||
version: link:../toolkit/language-kit
|
||||
'@logto/phrases':
|
||||
specifier: workspace:^1.2.0
|
||||
specifier: workspace:^1.3.0
|
||||
version: link:../phrases
|
||||
'@logto/phrases-ui':
|
||||
specifier: workspace:^1.2.0
|
||||
version: link:../phrases-ui
|
||||
'@logto/schemas':
|
||||
specifier: workspace:^1.3.1
|
||||
specifier: workspace:^1.4.0
|
||||
version: link:../schemas
|
||||
'@logto/shared':
|
||||
specifier: workspace:^2.0.0
|
||||
|
@ -3295,7 +3295,7 @@ importers:
|
|||
packages/create:
|
||||
dependencies:
|
||||
'@logto/cli':
|
||||
specifier: workspace:^1.3.1
|
||||
specifier: workspace:^1.4.0
|
||||
version: link:../cli
|
||||
|
||||
packages/demo-app:
|
||||
|
@ -3403,7 +3403,7 @@ importers:
|
|||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@logto/schemas':
|
||||
specifier: workspace:^1.1.0
|
||||
specifier: workspace:^1.4.0
|
||||
version: link:../schemas
|
||||
'@silverhand/eslint-config':
|
||||
specifier: 3.0.1
|
||||
|
@ -3540,7 +3540,7 @@ importers:
|
|||
specifier: workspace:^1.0.0
|
||||
version: link:../toolkit/language-kit
|
||||
'@logto/phrases':
|
||||
specifier: workspace:^1.2.0
|
||||
specifier: workspace:^1.3.0
|
||||
version: link:../phrases
|
||||
'@logto/phrases-ui':
|
||||
specifier: workspace:^1.2.0
|
||||
|
|
Loading…
Reference in a new issue