Skip to content

Commit

Permalink
refactor(core): add cache for cloud connection data
Browse files Browse the repository at this point in the history
  • Loading branch information
darcyYe committed Mar 19, 2024
1 parent 3ca0127 commit 8ea5a6b
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 49 deletions.
8 changes: 7 additions & 1 deletion packages/core/src/libraries/cloud-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,24 @@ const accessTokenExpirationMargin = 60;
export class CloudConnectionLibrary {
private client?: Client<typeof router>;
private accessTokenCache?: { expiresAt: number; accessToken: string };
private credentialsCache?: CloudConnection;

constructor(private readonly logtoConfigs: LogtoConfigLibrary) {}

public getCloudConnectionData = async (): Promise<CloudConnection> => {
if (this.credentialsCache) {
return this.credentialsCache;
}

const { getCloudConnectionData: getCloudServiceM2mCredentials } = this.logtoConfigs;
const credentials = await getCloudServiceM2mCredentials();
const { cloudUrlSet, adminUrlSet } = EnvSet.values;
return {
this.credentialsCache = {
...credentials,
tokenEndpoint: appendPath(adminUrlSet.endpoint, 'oidc/token').toString(),
endpoint: appendPath(cloudUrlSet.endpoint, 'api').toString(),
};
return this.credentialsCache;
};

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/libraries/logto-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
LogtoOidcConfigKey,
jwtCustomizerConfigGuard,
} from '@logto/schemas';
import type { LogtoOidcConfigType, LogtoJwtTokenKey } from '@logto/schemas';
import type { LogtoOidcConfigType, LogtoJwtTokenKey, CloudConnectionData } from '@logto/schemas';
import chalk from 'chalk';
import { z, ZodError } from 'zod';

Expand Down Expand Up @@ -53,7 +53,7 @@ export const createLogtoConfigLibrary = ({
}
};

const getCloudConnectionData = async () => {
const getCloudConnectionData = async (): Promise<CloudConnectionData> => {
const { value } = await queryCloudConnectionData();
const result = cloudConnectionDataGuard.safeParse(value);

Expand Down Expand Up @@ -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 };
Expand Down
87 changes: 45 additions & 42 deletions packages/core/src/oidc/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ export default function initOidc(
},
extraParams: [OIDCExtraParametersKey.InteractionMode],
extraTokenClaims: async (ctx, token) => {
if (!EnvSet.values.isDevFeaturesEnabled) {
const { isDevFeaturesEnabled, isCloud } = EnvSet.values;
// No cloud connection for OSS version, skip.
if (!isDevFeaturesEnabled || !isCloud) {
return;
}

Expand All @@ -226,48 +228,49 @@ export default function initOidc(
}
}

const {
value: { script, envVars },
} = (await trySafe(
logtoConfigs.getJwtCustomizer(
isTokenClientCredentials
? LogtoJwtTokenKey.ClientCredentials
: LogtoJwtTokenKey.AccessToken
)
)) ?? { value: {} };

if (script) {
// 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,
})
)) ?? {}
);
const { script, envVars } =
(await trySafe(
logtoConfigs.getJwtCustomizer(
isTokenClientCredentials
? LogtoJwtTokenKey.ClientCredentials
: LogtoJwtTokenKey.AccessToken
)
)) ?? {};

if (!script) {
return;
}

// 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: {
properties: Object.values(CustomClientMetadataKey),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/routes/logto-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ describe('configs routes', () => {

it('GET /configs/jwt-customizer/:tokenType should return the record', async () => {
logtoConfigLibraries.getJwtCustomizer.mockResolvedValueOnce(
mockJwtCustomizerConfigForAccessToken
mockJwtCustomizerConfigForAccessToken.value
);
const response = await routeRequester.get('/configs/jwt-customizer/access-token');
expect(response.status).toEqual(200);
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/routes/logto-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,11 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
const {
params: { tokenTypePath },
} = ctx.guard;
const { value } = await getJwtCustomizer(
ctx.body = await getJwtCustomizer(
tokenTypePath === LogtoJwtTokenPath.AccessToken
? LogtoJwtTokenKey.AccessToken
: LogtoJwtTokenKey.ClientCredentials
);
ctx.body = value;
return next();
}
);
Expand Down

0 comments on commit 8ea5a6b

Please sign in to comment.