mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
fix(core): mask password in audit log (#3130)
This commit is contained in:
parent
016833905d
commit
3b0bee717a
3 changed files with 59 additions and 1 deletions
5
.changeset-staged/afraid-eagles-retire.md
Normal file
5
.changeset-staged/afraid-eagles-retire.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/core": minor
|
||||
---
|
||||
|
||||
- mask sensitive password value in audit logs
|
|
@ -122,6 +122,44 @@ describe('koaAuditLog middleware', () => {
|
|||
expect(insertLog).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should filter password sensitive data in log', async () => {
|
||||
// @ts-expect-error for testing
|
||||
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
|
||||
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
|
||||
};
|
||||
ctx.request.ip = ip;
|
||||
|
||||
const additionalMockPayload = {
|
||||
password: '123456',
|
||||
interaction: { profile: { password: 123_456 } },
|
||||
};
|
||||
|
||||
const maskedAdditionalMockPayload = {
|
||||
password: '******',
|
||||
interaction: { profile: { password: '******' } },
|
||||
};
|
||||
|
||||
const next = async () => {
|
||||
const log = ctx.createLog(logKey);
|
||||
log.append(mockPayload);
|
||||
log.append(additionalMockPayload);
|
||||
};
|
||||
await koaLog(queries)(ctx, next);
|
||||
|
||||
expect(insertLog).toBeCalledWith({
|
||||
id: nanoIdMock,
|
||||
key: logKey,
|
||||
payload: {
|
||||
...mockPayload,
|
||||
...maskedAdditionalMockPayload,
|
||||
key: logKey,
|
||||
result: LogResult.Success,
|
||||
ip,
|
||||
userAgent,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('should insert an error log with the error message when next() throws an error', () => {
|
||||
it('should log with error message when next throws a normal Error', async () => {
|
||||
// @ts-expect-error for testing
|
||||
|
|
|
@ -8,6 +8,21 @@ import type { IRouterParamContext } from 'koa-router';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
|
||||
const filterSensitiveData = (data: Record<string, unknown>): Record<string, unknown> => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => {
|
||||
if (isRecord(value)) {
|
||||
return [key, filterSensitiveData(value)];
|
||||
}
|
||||
|
||||
return [key, key === 'password' ? '******' : value];
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const removeUndefinedKeys = (object: Record<string, unknown>) =>
|
||||
Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
|
||||
|
||||
|
@ -33,7 +48,7 @@ export class LogEntry {
|
|||
append(data: Readonly<LogPayload>) {
|
||||
this.payload = {
|
||||
...this.payload,
|
||||
...removeUndefinedKeys(data),
|
||||
...filterSensitiveData(removeUndefinedKeys(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue