mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
refactor(core): add cache for cloud connection data
This commit is contained in:
parent
89ba8a1a0c
commit
ddd93dc977
5 changed files with 56 additions and 48 deletions
|
@ -36,18 +36,24 @@ const accessTokenExpirationMargin = 60;
|
||||||
export class CloudConnectionLibrary {
|
export class CloudConnectionLibrary {
|
||||||
private client?: Client<typeof router>;
|
private client?: Client<typeof router>;
|
||||||
private accessTokenCache?: { expiresAt: number; accessToken: string };
|
private accessTokenCache?: { expiresAt: number; accessToken: string };
|
||||||
|
private credentialsCache?: CloudConnection;
|
||||||
|
|
||||||
constructor(private readonly logtoConfigs: LogtoConfigLibrary) {}
|
constructor(private readonly logtoConfigs: LogtoConfigLibrary) {}
|
||||||
|
|
||||||
public getCloudConnectionData = async (): Promise<CloudConnection> => {
|
public getCloudConnectionData = async (): Promise<CloudConnection> => {
|
||||||
|
if (this.credentialsCache) {
|
||||||
|
return this.credentialsCache;
|
||||||
|
}
|
||||||
|
|
||||||
const { getCloudConnectionData: getCloudServiceM2mCredentials } = this.logtoConfigs;
|
const { getCloudConnectionData: getCloudServiceM2mCredentials } = this.logtoConfigs;
|
||||||
const credentials = await getCloudServiceM2mCredentials();
|
const credentials = await getCloudServiceM2mCredentials();
|
||||||
const { cloudUrlSet, adminUrlSet } = EnvSet.values;
|
const { cloudUrlSet, adminUrlSet } = EnvSet.values;
|
||||||
return {
|
this.credentialsCache = {
|
||||||
...credentials,
|
...credentials,
|
||||||
tokenEndpoint: appendPath(adminUrlSet.endpoint, 'oidc/token').toString(),
|
tokenEndpoint: appendPath(adminUrlSet.endpoint, 'oidc/token').toString(),
|
||||||
endpoint: appendPath(cloudUrlSet.endpoint, 'api').toString(),
|
endpoint: appendPath(cloudUrlSet.endpoint, 'api').toString(),
|
||||||
};
|
};
|
||||||
|
return this.credentialsCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
LogtoOidcConfigKey,
|
LogtoOidcConfigKey,
|
||||||
jwtCustomizerConfigGuard,
|
jwtCustomizerConfigGuard,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import type { LogtoOidcConfigType, LogtoJwtTokenKey } from '@logto/schemas';
|
import type { LogtoOidcConfigType, LogtoJwtTokenKey, CloudConnectionData } from '@logto/schemas';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { z, ZodError } from 'zod';
|
import { z, ZodError } from 'zod';
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ export const createLogtoConfigLibrary = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCloudConnectionData = async () => {
|
const getCloudConnectionData = async (): Promise<CloudConnectionData> => {
|
||||||
const { value } = await queryCloudConnectionData();
|
const { value } = await queryCloudConnectionData();
|
||||||
const result = cloudConnectionDataGuard.safeParse(value);
|
const result = cloudConnectionDataGuard.safeParse(value);
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ export const createLogtoConfigLibrary = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return z.object({ value: jwtCustomizerConfigGuard[key] }).parse(rows[0]);
|
return z.object({ value: jwtCustomizerConfigGuard[key] }).parse(rows[0]).value;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { getOidcConfigs, getCloudConnectionData, upsertJwtCustomizer, getJwtCustomizer };
|
return { getOidcConfigs, getCloudConnectionData, upsertJwtCustomizer, getJwtCustomizer };
|
||||||
|
|
|
@ -210,7 +210,9 @@ export default function initOidc(
|
||||||
},
|
},
|
||||||
extraParams: [OIDCExtraParametersKey.InteractionMode],
|
extraParams: [OIDCExtraParametersKey.InteractionMode],
|
||||||
extraTokenClaims: async (ctx, token) => {
|
extraTokenClaims: async (ctx, token) => {
|
||||||
if (!EnvSet.values.isDevFeaturesEnabled) {
|
const { isDevFeaturesEnabled, isCloud } = EnvSet.values;
|
||||||
|
// No cloud connection for OSS version, skip.
|
||||||
|
if (!isDevFeaturesEnabled || !isCloud) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,48 +228,49 @@ export default function initOidc(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { script, envVars } =
|
||||||
value: { script, envVars },
|
(await trySafe(
|
||||||
} = (await trySafe(
|
logtoConfigs.getJwtCustomizer(
|
||||||
logtoConfigs.getJwtCustomizer(
|
isTokenClientCredentials
|
||||||
isTokenClientCredentials
|
? LogtoJwtTokenKey.ClientCredentials
|
||||||
? LogtoJwtTokenKey.ClientCredentials
|
: LogtoJwtTokenKey.AccessToken
|
||||||
: LogtoJwtTokenKey.AccessToken
|
)
|
||||||
)
|
)) ?? {};
|
||||||
)) ?? { value: {} };
|
|
||||||
|
|
||||||
if (script) {
|
if (!script) {
|
||||||
// Wait for cloud API to be ready and we can use cloud connection client to request the API.
|
return;
|
||||||
const client = await cloudConnection.getClient();
|
|
||||||
|
|
||||||
// We pass context to the cloud API only when it is a user's access token.
|
|
||||||
const logtoUserInfo = conditional(
|
|
||||||
!isTokenClientCredentials &&
|
|
||||||
token.accountId &&
|
|
||||||
(await libraries.jwtCustomizers.getUserContext(token.accountId))
|
|
||||||
);
|
|
||||||
/**
|
|
||||||
* `token` and `context` can not be assigned to Record<string, Json> according to the type inference,
|
|
||||||
* use request body guard to ensure the type.
|
|
||||||
*
|
|
||||||
* Use direct type casting to avoid the type inference issue since if the type is not correct the client
|
|
||||||
* will throw an Zod type error, there is no need to implement the zod guard and error handling here.
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
const payload = {
|
|
||||||
script,
|
|
||||||
envVars,
|
|
||||||
token,
|
|
||||||
context: conditional(logtoUserInfo && { user: logtoUserInfo }),
|
|
||||||
} as unknown as CustomJwtFetcher;
|
|
||||||
return (
|
|
||||||
(await trySafe(
|
|
||||||
client.post(`/api/services/custom-jwt`, {
|
|
||||||
body: payload,
|
|
||||||
})
|
|
||||||
)) ?? {}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for cloud API to be ready and we can use cloud connection client to request the API.
|
||||||
|
const client = await cloudConnection.getClient();
|
||||||
|
|
||||||
|
// We pass context to the cloud API only when it is a user's access token.
|
||||||
|
const logtoUserInfo = conditional(
|
||||||
|
!isTokenClientCredentials &&
|
||||||
|
token.accountId &&
|
||||||
|
(await libraries.jwtCustomizers.getUserContext(token.accountId))
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* `token` and `context` can not be assigned to Record<string, Json> according to the type inference,
|
||||||
|
* use request body guard to ensure the type.
|
||||||
|
*
|
||||||
|
* Use direct type casting to avoid the type inference issue since if the type is not correct the client
|
||||||
|
* will throw an Zod type error, there is no need to implement the zod guard and error handling here.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
const payload = {
|
||||||
|
script,
|
||||||
|
envVars,
|
||||||
|
token,
|
||||||
|
context: conditional(logtoUserInfo && { user: logtoUserInfo }),
|
||||||
|
} as unknown as CustomJwtFetcher;
|
||||||
|
return (
|
||||||
|
(await trySafe(
|
||||||
|
client.post(`/api/services/custom-jwt`, {
|
||||||
|
body: payload,
|
||||||
|
})
|
||||||
|
)) ?? {}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
extraClientMetadata: {
|
extraClientMetadata: {
|
||||||
properties: Object.values(CustomClientMetadataKey),
|
properties: Object.values(CustomClientMetadataKey),
|
||||||
|
|
|
@ -270,7 +270,7 @@ describe('configs routes', () => {
|
||||||
|
|
||||||
it('GET /configs/jwt-customizer/:tokenType should return the record', async () => {
|
it('GET /configs/jwt-customizer/:tokenType should return the record', async () => {
|
||||||
logtoConfigLibraries.getJwtCustomizer.mockResolvedValueOnce(
|
logtoConfigLibraries.getJwtCustomizer.mockResolvedValueOnce(
|
||||||
mockJwtCustomizerConfigForAccessToken
|
mockJwtCustomizerConfigForAccessToken.value
|
||||||
);
|
);
|
||||||
const response = await routeRequester.get('/configs/jwt-customizer/access-token');
|
const response = await routeRequester.get('/configs/jwt-customizer/access-token');
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
|
|
|
@ -257,12 +257,11 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
|
||||||
const {
|
const {
|
||||||
params: { tokenTypePath },
|
params: { tokenTypePath },
|
||||||
} = ctx.guard;
|
} = ctx.guard;
|
||||||
const { value } = await getJwtCustomizer(
|
ctx.body = await getJwtCustomizer(
|
||||||
tokenTypePath === LogtoJwtTokenPath.AccessToken
|
tokenTypePath === LogtoJwtTokenPath.AccessToken
|
||||||
? LogtoJwtTokenKey.AccessToken
|
? LogtoJwtTokenKey.AccessToken
|
||||||
: LogtoJwtTokenKey.ClientCredentials
|
: LogtoJwtTokenKey.ClientCredentials
|
||||||
);
|
);
|
||||||
ctx.body = value;
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue