mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
feat(core): add SAML app audit logs (#6931)
This commit is contained in:
parent
d16666a471
commit
837324a015
4 changed files with 62 additions and 1 deletions
|
@ -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>> & {
|
||||
|
|
|
@ -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';
|
||||
|
@ -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({
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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'
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -226,6 +263,7 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
|
|||
});
|
||||
|
||||
const extractResult = authRequestInfoGuard.safeParse(loginRequestResult.extract);
|
||||
log.append({ extractResult });
|
||||
|
||||
if (!extractResult.success) {
|
||||
throw new RequestError({
|
||||
|
@ -233,6 +271,7 @@ export default function samlApplicationAnonymousRoutes<T extends AnonymousRouter
|
|||
error: extractResult.error.flatten(),
|
||||
});
|
||||
}
|
||||
log.append({ extractResultData: extractResult.data });
|
||||
|
||||
assertThat(
|
||||
extractResult.data.issuer === samlApplication.details.entityId,
|
||||
|
@ -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) {
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
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.
|
||||
|
@ -21,5 +24,6 @@ export type JwtCustomizerLogKey = jwtCustomizer.LogKey;
|
|||
*
|
||||
* @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
packages/schemas/src/types/log/saml.ts
Normal file
10
packages/schemas/src/types/log/saml.ts
Normal file
|
@ -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}`;
|
Loading…
Add table
Reference in a new issue