mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-24 11:36:37 +08:00
feat(auth): org session policies — lifetime/idle limits, org-wide revocation (#5862)
* feat(auth): org session policies — lifetime/idle limits, org-wide revocation, cookie-cache versioning * refactor(auth): consolidate session-policy clamp semantics, shared security-policy version module, canonical bounds, docs * polish(session-policy): cleanup pass — muted field labels, spinner reset, state tracker, response-seeded baseline, comment trims * fix(session-policy): govern member sessions by membership (closes revoke cookie-cache hole), normalize createdAt, remount on org switch, sync audit mock * fix(session-policy): clamp pre-join sessions on invite acceptance, normalize expiresAt, sync unified nav test * fix(session-policy): invalidate membership cache on removal/transfer, spare impersonator sessions in revoke-all, raise idle floor to 2x cookie window * fix(session-policy): resolve governing org by membership only — activeOrganizationId goes stale across transfer/leave * fix(session-policy): atomic policy save + eager clamp, asymmetric membership TTL, admin-add cache invalidation * fix(session-policy): org-scoped cookie version string, atomic revoke delete+bump * fix(session-policy): plan-gate effective policy so downgraded orgs stop enforcing automatically * chore(session-policy): drop dead bumpSecurityPolicyVersion helper — call sites bump transactionally * fix(session-policy): unify join paths on applySessionPolicyToNewMember; final audit polish (dead exports, response bound, test name)
This commit is contained in:
@@ -131,6 +131,8 @@ export const AuditAction = {
|
||||
// Organizations
|
||||
ORGANIZATION_CREATED: 'organization.created',
|
||||
ORGANIZATION_UPDATED: 'organization.updated',
|
||||
ORGANIZATION_SESSION_POLICY_UPDATED: 'organization.session_policy.updated',
|
||||
ORGANIZATION_SESSIONS_REVOKED: 'organization.sessions.revoked',
|
||||
ORG_MEMBER_ADDED: 'org_member.added',
|
||||
ORG_MEMBER_REMOVED: 'org_member.removed',
|
||||
ORG_MEMBER_ROLE_CHANGED: 'org_member.role_changed',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "organization" ADD COLUMN "session_policy_settings" json;--> statement-breakpoint
|
||||
ALTER TABLE "organization" ADD COLUMN "security_policy_version" integer DEFAULT 1 NOT NULL;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1849,6 +1849,13 @@
|
||||
"when": 1784680777879,
|
||||
"tag": "0264_fat_ikaris",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 265,
|
||||
"version": "7",
|
||||
"when": 1784756534601,
|
||||
"tag": "0265_org_session_policy",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1230,12 +1230,36 @@ export interface DataRetentionSettings {
|
||||
retentionOverrides?: RetentionOverride[] | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Org-level session policy (enterprise). Absent or empty = Better Auth
|
||||
* defaults (30-day sliding sessions). `maxSessionHours` caps absolute session
|
||||
* lifetime from creation; `idleTimeoutHours` caps time between refreshes.
|
||||
* Enforced by clamping `session.expiresAt` in the Better Auth session
|
||||
* create/update database hooks; `securityPolicyVersion` invalidates cached
|
||||
* session cookies org-wide when bumped.
|
||||
*/
|
||||
export interface SessionPolicySettings {
|
||||
/** Absolute session lifetime cap in hours from session creation. */
|
||||
maxSessionHours?: number | null
|
||||
/** Idle timeout in hours — session expires this long after its last refresh. */
|
||||
idleTimeoutHours?: number | null
|
||||
}
|
||||
|
||||
export const organization = pgTable('organization', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
slug: text('slug').notNull(),
|
||||
logo: text('logo'),
|
||||
metadata: json('metadata'),
|
||||
sessionPolicySettings: json('session_policy_settings').$type<SessionPolicySettings>(),
|
||||
/**
|
||||
* Monotonic counter embedded in the Better Auth cookie-cache version for
|
||||
* this org's members. Bumped on any security-policy change or org-wide
|
||||
* session revocation so every cached session cookie in the org is
|
||||
* invalidated (falls through to a DB session read) within the policy
|
||||
* cache TTL instead of the 24h cookie-cache lifetime.
|
||||
*/
|
||||
securityPolicyVersion: integer('security_policy_version').notNull().default(1),
|
||||
whitelabelSettings: json('whitelabel_settings').$type<{
|
||||
brandName?: string
|
||||
logoUrl?: string
|
||||
|
||||
@@ -105,6 +105,8 @@ export const auditMock = {
|
||||
PASSWORD_RESET_REQUESTED: 'password.reset_requested',
|
||||
ORGANIZATION_CREATED: 'organization.created',
|
||||
ORGANIZATION_UPDATED: 'organization.updated',
|
||||
ORGANIZATION_SESSION_POLICY_UPDATED: 'organization.session_policy.updated',
|
||||
ORGANIZATION_SESSIONS_REVOKED: 'organization.sessions.revoked',
|
||||
ORG_MEMBER_ADDED: 'org_member.added',
|
||||
ORG_MEMBER_REMOVED: 'org_member.removed',
|
||||
ORG_MEMBER_ROLE_CHANGED: 'org_member.role_changed',
|
||||
|
||||
Reference in New Issue
Block a user