mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Compare commits
2 Commits
main
...
cursor/aut
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe50f7c2d | ||
|
|
22a030ac02 |
@@ -82,6 +82,37 @@ const logger = createLogger('Auth')
|
||||
import { getMicrosoftRefreshTokenExpiry, isMicrosoftProvider } from '@/lib/oauth/microsoft'
|
||||
import { getCanonicalScopesForProvider } from '@/lib/oauth/utils'
|
||||
|
||||
/**
|
||||
* Extracts user info from a Microsoft ID token JWT instead of calling Graph API /me.
|
||||
* This avoids 403 errors for external tenant users whose admin hasn't consented to Graph API scopes.
|
||||
* The ID token is always returned when the openid scope is requested.
|
||||
*/
|
||||
function getMicrosoftUserInfoFromIdToken(tokens: { accessToken: string }, providerId: string) {
|
||||
const idToken = (tokens as Record<string, unknown>).idToken as string | undefined
|
||||
if (!idToken) {
|
||||
logger.error(
|
||||
`Microsoft ${providerId} OAuth: no ID token received. Ensure openid scope is requested.`
|
||||
)
|
||||
throw new Error(`Microsoft ${providerId} OAuth requires an ID token (openid scope)`)
|
||||
}
|
||||
|
||||
const parts = idToken.split('.')
|
||||
if (parts.length !== 3) {
|
||||
throw new Error(`Microsoft ${providerId} OAuth: malformed ID token`)
|
||||
}
|
||||
|
||||
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'))
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${payload.oid || payload.sub}-${crypto.randomUUID()}`,
|
||||
name: payload.name || 'Microsoft User',
|
||||
email: payload.email || payload.preferred_username || payload.upn,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
}
|
||||
|
||||
const blockedSignupDomains = env.BLOCKED_SIGNUP_DOMAINS
|
||||
? new Set(env.BLOCKED_SIGNUP_DOMAINS.split(',').map((d) => d.trim().toLowerCase()))
|
||||
: null
|
||||
@@ -1291,29 +1322,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/microsoft-ad`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'microsoft-ad')
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1331,29 +1340,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/microsoft-teams`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'microsoft-teams')
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1371,29 +1358,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/microsoft-excel`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'microsoft-excel')
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1410,32 +1375,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/microsoft-dataverse`,
|
||||
getUserInfo: async (tokens) => {
|
||||
// Dataverse access tokens target dynamics.microsoft.com, not graph.microsoft.com,
|
||||
// so we cannot call the Graph API /me endpoint. Instead, we decode the ID token JWT
|
||||
// which is always returned when the openid scope is requested.
|
||||
const idToken = (tokens as Record<string, unknown>).idToken as string | undefined
|
||||
if (!idToken) {
|
||||
logger.error(
|
||||
'Microsoft Dataverse OAuth: no ID token received. Ensure openid scope is requested.'
|
||||
)
|
||||
throw new Error('Microsoft Dataverse OAuth requires an ID token (openid scope)')
|
||||
}
|
||||
|
||||
const parts = idToken.split('.')
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('Microsoft Dataverse OAuth: malformed ID token')
|
||||
}
|
||||
|
||||
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'))
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${payload.oid || payload.sub}-${crypto.randomUUID()}`,
|
||||
name: payload.name || 'Microsoft User',
|
||||
email: payload.preferred_username || payload.email || payload.upn,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'microsoft-dataverse')
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1452,29 +1392,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/microsoft-planner`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'microsoft-planner')
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1492,29 +1410,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/outlook`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'outlook')
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1532,29 +1428,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/onedrive`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'onedrive')
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1572,29 +1446,7 @@ export const auth = betterAuth({
|
||||
pkce: true,
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/sharepoint`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
})
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch Microsoft user info', { status: response.status })
|
||||
throw new Error(`Failed to fetch Microsoft user info: ${response.statusText}`)
|
||||
}
|
||||
const profile = await response.json()
|
||||
const now = new Date()
|
||||
return {
|
||||
id: `${profile.id}-${crypto.randomUUID()}`,
|
||||
name: profile.displayName || 'Microsoft User',
|
||||
email: profile.mail || profile.userPrincipalName,
|
||||
emailVerified: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Microsoft getUserInfo', { error })
|
||||
throw error
|
||||
}
|
||||
return getMicrosoftUserInfoFromIdToken(tokens, 'sharepoint')
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user