0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

Merge pull request #6312 from logto-io/gao-log-app-secret

refactor(core): log app secret name
This commit is contained in:
Gao Sun 2024-07-24 17:37:30 +08:00 committed by GitHub
commit 98dbead1bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 49 additions and 5 deletions

View file

@ -1,6 +1,7 @@
import { GrantType, LogResult, token } from '@logto/schemas';
import type { errors, KoaContextWithOIDC } from 'oidc-provider';
import { type WithAppSecretContext } from '#src/middleware/koa-app-secret-transpilation.js';
import type { WithLogContext } from '#src/middleware/koa-audit-log.js';
import { stringifyError } from '../utils/format.js';
@ -13,7 +14,7 @@ import { extractInteractionContext } from './utils.js';
* @see {@link https://github.com/panva/node-oidc-provider/blob/v7.x/lib/shared/error_handler.js OIDC Provider error handler}
*/
export const grantListener = (
ctx: KoaContextWithOIDC & WithLogContext & { body: GrantBody },
ctx: KoaContextWithOIDC & WithLogContext & WithAppSecretContext & { body: GrantBody },
error?: errors.OIDCProviderError
) => {
const { params } = ctx.oidc;

View file

@ -1,10 +1,11 @@
import type { IRouterParamContext } from 'koa-router';
import type { KoaContextWithOIDC } from 'oidc-provider';
import { type WithAppSecretContext } from '#src/middleware/koa-app-secret-transpilation.js';
import type { LogPayload } from '#src/middleware/koa-audit-log.js';
export const extractInteractionContext = (
ctx: IRouterParamContext & KoaContextWithOIDC
ctx: IRouterParamContext & KoaContextWithOIDC & WithAppSecretContext
): LogPayload => {
const {
entities: { Account, Session, Client, Interaction },
@ -13,6 +14,7 @@ export const extractInteractionContext = (
return {
applicationId: Client?.clientId,
applicationSecret: ctx.appSecret,
sessionId: Session?.jti,
interactionId: Interaction?.jti,
userId: Account?.accountId,

View file

@ -1,5 +1,5 @@
import type { Nullable } from '@silverhand/essentials';
import type { MiddlewareType } from 'koa';
import type { Context, MiddlewareType } from 'koa';
import { errors } from 'oidc-provider';
import type Queries from '#src/tenants/Queries.js';
@ -14,6 +14,14 @@ function decodeAuthToken(token: string) {
return authToken;
}
export type WithAppSecretContext<ContextT = Context> = ContextT & {
/** The application secret that has been transpiled during the middleware execution. */
appSecret?: {
/** The application secret name of the application (client). */
name: string;
};
};
/** @import { getConstantClientMetadata } from '#src/oidc/utils.js'; */
/**
* Create a middleware function that reads the app secret from the request and check if it matches
@ -39,7 +47,7 @@ function decodeAuthToken(token: string) {
*/
export default function koaAppSecretTranspilation<StateT, ContextT, ResponseBodyT>(
queries: Queries
): MiddlewareType<StateT, ContextT, Nullable<ResponseBodyT>> {
): MiddlewareType<StateT, WithAppSecretContext<ContextT>, Nullable<ResponseBodyT>> {
return async (ctx, next) => {
type ClientCredentials = Partial<{
clientId: string;
@ -127,6 +135,7 @@ export default function koaAppSecretTranspilation<StateT, ContextT, ResponseBody
ctx.query.client_secret = result.originalSecret;
}
ctx.appSecret = { name: result.name };
return next();
};
}

View file

@ -8,7 +8,7 @@
import assert from 'node:assert';
import { ApplicationType } from '@logto/schemas';
import { ApplicationType, token } from '@logto/schemas';
import { noop, removeUndefinedKeys } from '@silverhand/essentials';
import { HTTPError } from 'ky';
@ -18,6 +18,7 @@ import {
createApplicationSecret,
deleteApplication,
} from '#src/api/application.js';
import { getAuditLogs } from '#src/api/index.js';
import { createResource } from '#src/api/resource.js';
import { devFeatureTest, randomString, waitFor } from '#src/utils.js';
@ -33,6 +34,23 @@ const [application, resource] = await Promise.all([
createResource(),
]);
const getLogs = async () =>
getAuditLogs(
new URLSearchParams({
logKey: `${token.Type.ExchangeTokenBy}.${token.ExchangeByType.ClientCredentials}`,
})
);
const expectLog = (applicationId: string, secretName: string) =>
expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
payload: expect.objectContaining({
applicationId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
applicationSecret: expect.objectContaining({ name: secretName }),
}),
});
afterAll(async () => {
await deleteApplication(application.id).catch(noop);
});
@ -155,11 +173,14 @@ devFeatureTest.describe('client authentication', () => {
});
it('should pass when client credentials are valid in authorization header', async () => {
const application = await createApplication('application', ApplicationType.MachineToMachine);
const secret = await createApplicationSecret({
applicationId: application.id,
name: randomString(),
});
const beforeLogs = await getLogs();
expect(beforeLogs).not.toContainEqual(expectLog(application.id, secret.name));
await expect(
post({
authorization: `Basic ${Buffer.from(`${application.id}:${secret.value}`).toString(
@ -170,14 +191,21 @@ devFeatureTest.describe('client authentication', () => {
).resolves.toMatchObject({
token_type: 'Bearer',
});
const logs = await getLogs();
expect(logs).toContainEqual(expectLog(application.id, secret.name));
await deleteApplication(application.id);
});
it('should pass when client credentials are valid in body', async () => {
const application = await createApplication('application', ApplicationType.MachineToMachine);
const secret = await createApplicationSecret({
applicationId: application.id,
name: randomString(),
});
const beforeLogs = await getLogs();
expect(beforeLogs).not.toContainEqual(expectLog(application.id, secret.name));
await expect(
post({
body: {
@ -206,5 +234,9 @@ devFeatureTest.describe('client authentication', () => {
token_type: 'Bearer',
});
}
const logs = await getLogs();
expect(logs).toContainEqual(expectLog(application.id, secret.name));
await deleteApplication(application.id);
});
});