fix(auth): end OAuth grants on SCIM address change and add sign out other devices by waleedlatif1 · Pull Request #9181 · simstudioai/sim · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/sim/ee/scim/lib/application/users/provision-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ScimUserAttributes } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { APIError } from 'better-auth/api'
import { auth } from '@/lib/auth'
import { revokeUserOAuthGrants } from '@/lib/auth/oauth-access-token'
import { applySessionPolicyToNewMember } from '@/lib/auth/session-policy'
import { syncUsageLimitsFromSubscription } from '@/lib/billing/core/usage'
import { ensureUserInOrganizationTx } from '@/lib/billing/organizations/membership'
Expand Down Expand Up @@ -184,8 +185,9 @@ export const provisionScimUser = defineAuthorizedScimUseCase({
email,
name: accountName(attributes),
})
if (emailChanged && attributes.active) {
if (emailChanged) {
await revokeUserSessionsTx(tx, { userId, organizationId: context.organizationId })
await revokeUserOAuthGrants(userId, tx)
}
/** A relinked account may still carry the suspension a lost deprovisioning left behind. */
if (attributes.active) await unsuspendMemberTx(tx, { userId, source: 'scim' })
Expand Down
18 changes: 9 additions & 9 deletions apps/sim/ee/scim/lib/application/users/update-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { db } from '@sim/db'
import type { ScimUserAttributes } from '@sim/db/schema'
import { normalizeEmail } from '@sim/utils/string'
import type { ScimPatchOperation } from '@/lib/api/contracts/scim'
import { revokeUserOAuthGrants } from '@/lib/auth/oauth-access-token'
import { acquireOrganizationUserMutationLocks } from '@/lib/billing/organizations/membership'
import type { DbOrTx, DbTransaction } from '@/lib/db/types'
import type { StoppedRun } from '@/lib/mothership/async-runs/repository'
Expand Down Expand Up @@ -86,16 +87,15 @@ async function applyUserUpdate(
})

/**
* An address change ends the sessions established under the old one. A
* deactivation in the same request revokes them itself, so this only runs
* when nothing else will.
* An address change ends the sessions and OAuth grants established under
* the old one. Sessions go first: issuance references its session, so no
* grant can be minted behind the sweep.
*/
if (!deactivated) {
await revokeUserSessionsTx(tx, {
userId: current.userId,
organizationId: context.organizationId,
})
}
await revokeUserSessionsTx(tx, {
userId: current.userId,
organizationId: context.organizationId,
})
await revokeUserOAuthGrants(current.userId, tx)
} else if (nextName !== current.name) {
await syncAccountIdentityTx(tx, { userId: current.userId, name: nextName })
}
Expand Down
14 changes: 14 additions & 0 deletions apps/sim/hooks/queries/user-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type UpdateUserProfileBody,
updateUserProfileContract,
} from '@/lib/api/contracts/user'
import { client } from '@/lib/auth/client'
import {
mapUserProfileResponse,
USER_PROFILE_STALE_TIME,
Expand Down Expand Up @@ -88,3 +89,16 @@ export function useResetPassword() {
},
})
}

/**
* Ends every session except this one. Better Auth's plugin endpoint is called through the auth
* client, as no Sim contract types it.
*/
export function useSignOutOtherDevices() {
return useMutation({
mutationFn: async () => {
const { error } = await client.revokeOtherSessions()
if (error) throw new Error(error.message ?? 'Failed to sign out other devices')
},
})
}
67 changes: 67 additions & 0 deletions apps/sim/scripts/test-scim-e2e.ts
Loading