feat(portal): real Security & audit page (+ bundled Storage / per-tenant-roles WIP)
Security & audit (admin) - Audit log: real, tenant-scoped — widened GET /tenants/:slug/audit with q/action/outcome/actorEmail/since/before; UI gains search, outcome + time filters, action chips, cursor pagination, and client-side CSV export. - Security policy: new tenant.securityPolicy (mfaMode, session idle/absolute, allowedCountries, ipAllowlist) + PATCH /tenants/:slug/security-policy (membership-gated, audited). Editable, labelled by enforcement status. - MFA: live enrollment overview via GET /tenants/:slug/mfa-status (Authentik countAuthenticators per member). - SSO apps (Dezky as IdP): real Authentik OIDC provider + application CRUD, scoped to the tenant group. New AuthentikClient methods (provider/app/binding + flow/key/scope discovery), TenantSsoApp schema, TenantSsoService (rollback on partial failure; client secret never stored), GET/POST/DELETE /tenants/:slug/sso-apps. Validated end-to-end against live Authentik. - Deferred: shared-flow MFA/geo/session enforcement (global auth-flow blast radius) — to be done as its own reviewed change. Bundled in-progress work that shares the same files (kept together so the tree stays green): - Storage page: StorageService + GET /tenants/:slug/storage (OCIS-backed), storage.get proxy, storage.vue. - Per-tenant roles: User.tenantRoles + MeProfile.tenantRoles plumbing.
This commit is contained in:
@@ -78,6 +78,29 @@ export class Tenant {
|
||||
contactEmail?: string
|
||||
}
|
||||
|
||||
// Customer-managed security policy. Stored intent; enforcement is wired
|
||||
// incrementally (MFA enrollment is read live; MFA/geo/session enforcement
|
||||
// via Authentik lands in a later stage). mfaMode: 'all' | 'admins' |
|
||||
// 'optional'. Timeouts in minutes/hours. allowedCountries = ISO alpha-2;
|
||||
// ipAllowlist = CIDR strings.
|
||||
@Prop({
|
||||
type: {
|
||||
mfaMode: { type: String, enum: ['all', 'admins', 'optional'], default: 'optional' },
|
||||
sessionIdleMinutes: { type: Number, min: 0 },
|
||||
sessionAbsoluteHours: { type: Number, min: 0 },
|
||||
allowedCountries: { type: [String], default: undefined },
|
||||
ipAllowlist: { type: [String], default: undefined },
|
||||
},
|
||||
default: () => ({ mfaMode: 'optional' }),
|
||||
})
|
||||
securityPolicy!: {
|
||||
mfaMode: 'all' | 'admins' | 'optional'
|
||||
sessionIdleMinutes?: number
|
||||
sessionAbsoluteHours?: number
|
||||
allowedCountries?: string[]
|
||||
ipAllowlist?: string[]
|
||||
}
|
||||
|
||||
// Per-integration provisioning state. Each one is updated independently when its
|
||||
// upstream API call succeeds or fails — orchestration is best-effort, not atomic.
|
||||
@Prop({
|
||||
|
||||
Reference in New Issue
Block a user