Skip to content

Commit 2640869

Browse files
committed
chore(release): merge staging into the v0.9.41 release
2 parents 93611f2 + eb6bcdb commit 2640869

2 files changed

Lines changed: 61 additions & 1 deletion

File tree

‎apps/sim/lib/auth/sso/application/admit-sso-user.integration.ts‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,48 @@ describe('SSO admission with a hosted SCIM directory in PostgreSQL', () => {
207207
expect(await membership()).toEqual([{ id: memberId, role: 'admin' }])
208208
})
209209

210+
async function emailVerified() {
211+
const { db, schema, eq } = runtime
212+
const [row] = await db
213+
.select({ emailVerified: schema.user.emailVerified })
214+
.from(schema.user)
215+
.where(eq(schema.user.id, userId))
216+
return row?.emailVerified
217+
}
218+
219+
it('verifies a directory-provisioned member who signs in through the verified-domain provider', async () => {
220+
const { db, schema, eq } = runtime
221+
const memberId = generateId()
222+
await db.insert(schema.member).values({ id: memberId, userId, organizationId, role: 'member' })
223+
await db.update(schema.user).set({ emailVerified: false }).where(eq(schema.user.id, userId))
224+
225+
await expect(admit()).resolves.toEqual({ kind: 'already-member', organizationId, memberId })
226+
expect(await emailVerified()).toBe(true)
227+
})
228+
229+
it('leaves an address outside the provider domain unverified', async () => {
230+
const { db, schema, eq } = runtime
231+
await db
232+
.update(schema.user)
233+
.set({ emailVerified: false, email: `${userId}@elsewhere.test` })
234+
.where(eq(schema.user.id, userId))
235+
236+
await expect(admit()).resolves.toEqual({ kind: 'denied', reason: 'domain-mismatch' })
237+
expect(await emailVerified()).toBe(false)
238+
})
239+
240+
it('leaves the address unverified when the provider domain is not proven', async () => {
241+
const { db, schema, eq } = runtime
242+
await db.update(schema.user).set({ emailVerified: false }).where(eq(schema.user.id, userId))
243+
await db
244+
.update(schema.ssoProvider)
245+
.set({ domainVerified: false })
246+
.where(eq(schema.ssoProvider.providerId, providerId))
247+
248+
await expect(admit()).resolves.toEqual({ kind: 'denied', reason: 'provider-not-trusted' })
249+
expect(await emailVerified()).toBe(false)
250+
})
251+
210252
it('resumes normal JIT admission when the directory permits automatic membership', async () => {
211253
const { db, schema, eq } = runtime
212254
await db

‎apps/sim/lib/auth/sso/application/admit-sso-user.ts‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ async function runAdmissionTransaction(
110110

111111
const [[userRow], [linkedAccount]] = await Promise.all([
112112
tx
113-
.select({ id: user.id, email: user.email, name: user.name })
113+
.select({
114+
id: user.id,
115+
email: user.email,
116+
name: user.name,
117+
emailVerified: user.emailVerified,
118+
})
114119
.from(user)
115120
.where(eq(user.id, userId))
116121
.limit(1),
@@ -133,6 +138,19 @@ async function runAdmissionTransaction(
133138
return { providerId, result: { kind: 'denied', reason: 'domain-mismatch' } }
134139
}
135140

141+
/**
142+
* `trustEmailVerified` stays off, so Better Auth always stores an SSO user as
143+
* unverified. Signing in through a linked provider on a DNS-verified domain
144+
* that matches the address is the ownership proof, and without it SSO- and
145+
* SCIM-created users can never connect accounts.
146+
*/
147+
if (!userRow.emailVerified) {
148+
await tx
149+
.update(user)
150+
.set({ emailVerified: true, updatedAt: new Date() })
151+
.where(and(eq(user.id, userId), eq(user.email, userRow.email)))
152+
}
153+
136154
const attribution = { providerId, userName: userRow.name, userEmail: userRow.email }
137155
if (!provider.organizationId) {
138156
return {

0 commit comments

Comments
 (0)