Skip to content

Commit

Permalink
feat(core): add SAML app audit logs (#6931)
Browse files Browse the repository at this point in the history
  • Loading branch information
darcyYe authored Jan 10, 2025
1 parent d16666a commit 837324a
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/console/src/consts/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export const auditLogEventTitle: Record<string, Optional<string>> & {
'Create IdP-initiated SAML SSO authentication session',
'JwtCustomizer.AccessToken': 'Get custom user access token claims',
'JwtCustomizer.ClientCredential': 'Get custom M2M access token claims',
'SamlApplication.AuthnRequest': 'Receive SAML application authentication request',
'SamlApplication.Callback': 'Handle SAML application callback',
});

export const logEventTitle: Record<string, Optional<string>> & {
Expand Down
45 changes: 45 additions & 0 deletions packages/core/src/saml-applications/routes/anonymous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { z } from 'zod';

import { spInitiatedSamlSsoSessionCookieName } from '#src/constants/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import koaAuditLog from '#src/middleware/koa-audit-log.js';
import koaGuard from '#src/middleware/koa-guard.js';
import type { AnonymousRouter, RouterInitArgs } from '#src/routes/types.js';
import assertThat from '#src/utils/assert-that.js';
Expand Down Expand Up @@ -62,12 +63,20 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
query: samlApplicationSignInCallbackQueryParametersGuard,
status: [200, 400],
}),
koaAuditLog(queries),
async (ctx, next) => {
const {
params: { id },
query,
} = ctx.guard;

const log = ctx.createLog('SamlApplication.Callback');

log.append({
query,
samlApplicationId: id,
});

// Handle error in query parameters
if ('error' in query) {
throw new RequestError({
Expand All @@ -94,6 +103,11 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter

const { context, entityEndpoint } = await samlApplication.createSamlResponse(userInfo);

log.append({
context,
entityEndpoint,
});

// Return auto-submit form
ctx.body = generateAutoSubmitForm(entityEndpoint, context);
return next();
Expand All @@ -115,12 +129,19 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
.catchall(z.string()),
status: [200, 302, 400, 404],
}),
koaAuditLog(queries),
async (ctx, next) => {
const {
params: { id },
query: { Signature, RelayState, ...rest },
} = ctx.guard;

const log = ctx.createLog('SamlApplication.AuthnRequest');
log.append({
query: ctx.guard.query,
samlApplicationId: id,
});

const details = await getSamlApplicationDetailsById(id);
const samlApplication = new SamlApplication(details, id, envSet.oidc.issuer, tenantId);

Expand All @@ -142,6 +163,7 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
});

const extractResult = authRequestInfoGuard.safeParse(loginRequestResult.extract);
log.append({ extractResult });

if (!extractResult.success) {
throw new RequestError({
Expand All @@ -150,6 +172,8 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
});
}

log.append({ extractResultData: extractResult.data });

assertThat(
extractResult.data.issuer === samlApplication.details.entityId,
'application.saml.auth_request_issuer_not_match'
Expand Down Expand Up @@ -182,6 +206,12 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
overwrite: true,
});

log.append({
cookie: {
spInitiatedSamlSsoSessionCookieName: insertSamlAppSession,
},
});

ctx.redirect(signInUrl.toString());
} catch (error: unknown) {
if (error instanceof RequestError) {
Expand All @@ -208,12 +238,19 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
}),
status: [200, 302, 400, 404],
}),
koaAuditLog(queries),
async (ctx, next) => {
const {
params: { id },
body: { SAMLRequest, RelayState },
} = ctx.guard;

const log = ctx.createLog('SamlApplication.AuthnRequest');
log.append({
body: ctx.guard.body,
samlApplicationId: id,
});

const details = await getSamlApplicationDetailsById(id);
const samlApplication = new SamlApplication(details, id, envSet.oidc.issuer, tenantId);

Expand All @@ -226,13 +263,15 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
});

const extractResult = authRequestInfoGuard.safeParse(loginRequestResult.extract);
log.append({ extractResult });

if (!extractResult.success) {
throw new RequestError({
code: 'application.saml.invalid_saml_request',
error: extractResult.error.flatten(),
});
}
log.append({ extractResultData: extractResult.data });

assertThat(
extractResult.data.issuer === samlApplication.details.entityId,
Expand Down Expand Up @@ -264,6 +303,12 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
overwrite: true,
});

log.append({
cookie: {
spInitiatedSamlSsoSessionCookieName: insertSamlAppSession,
},
});

ctx.redirect(signInUrl.toString());
} catch (error: unknown) {
if (error instanceof RequestError) {
Expand Down
6 changes: 5 additions & 1 deletion packages/schemas/src/types/log/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import type * as hook from './hook.js';
import type * as interaction from './interaction.js';
import type * as jwtCustomizer from './jwt-customizer.js';
import type * as saml from './saml.js';
import type * as token from './token.js';

export * as interaction from './interaction.js';
export * as token from './token.js';
export * as hook from './hook.js';
export * as jwtCustomizer from './jwt-customizer.js';
export * as saml from './saml.js';

/** Fallback for empty or unrecognized log keys. */
export const LogKeyUnknown = 'Unknown';

export type AuditLogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey;
export type AuditLogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey | saml.LogKey;
export type WebhookLogKey = hook.LogKey;
export type JwtCustomizerLogKey = jwtCustomizer.LogKey;
export type SamlLogKey = saml.LogKey;

/**
* The union type of all available log keys.
* Note duplicate keys are allowed but should be avoided.
*
* @see {@link interaction.LogKey} for interaction log keys.
* @see {@link token.LogKey} for token log keys.
* @see {@link saml.LogKey} for SAML application log keys.
**/
export type LogKey = AuditLogKey | WebhookLogKey | JwtCustomizerLogKey;
10 changes: 10 additions & 0 deletions packages/schemas/src/types/log/saml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type Prefix = 'SamlApplication';

export const prefix: Prefix = 'SamlApplication';

export enum Scenario {
Callback = 'Callback',
AuthnRequest = 'AuthnRequest',
}

export type LogKey = `${Prefix}.${Scenario}`;

0 comments on commit 837324a

Please sign in to comment.