6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { createLogger } from '@sim/logger'
|
|
import { generateId } from '@sim/utils/id'
|
|
|
|
/**
|
|
* Scoped `'Auth'` because these lines are emitted from the OAuth callback path
|
|
* and were logged under that scope before this helper moved here; renaming the
|
|
* scope would break existing log queries and alerts.
|
|
*/
|
|
const logger = createLogger('Auth')
|
|
|
|
const MICROSOFT_REFRESH_TOKEN_LIFETIME_DAYS = 90
|
|
export const PROACTIVE_REFRESH_THRESHOLD_DAYS = 7
|
|
|
|
export const MICROSOFT_PROVIDERS = new Set([
|
|
'microsoft-ad',
|
|
'microsoft-dataverse',
|
|
'microsoft-excel',
|
|
'microsoft-planner',
|
|
'microsoft-teams',
|
|
'outlook',
|
|
'onedrive',
|
|
'sharepoint',
|
|
])
|
|
|
|
export function isMicrosoftProvider(providerId: string): boolean {
|
|
return MICROSOFT_PROVIDERS.has(providerId)
|
|
}
|
|
|
|
export function getMicrosoftRefreshTokenExpiry(): Date {
|
|
return new Date(Date.now() + MICROSOFT_REFRESH_TOKEN_LIFETIME_DAYS * 24 * 60 * 60 * 1000)
|
|
}
|
|
|
|
/**
|
|
* Derives whether a Microsoft ID token proves ownership of `email`. Azure AD's
|
|
* `email`/`upn` claims are unverified and mutable on multi-tenant (`/common/`)
|
|
* endpoints, so the email is trusted only when the token explicitly proves it via
|
|
* the `email_verified` claim or the verified-email claims, mirroring Better
|
|
* Auth's built-in Microsoft provider. Defaults to `false` when no claim asserts
|
|
* verification, so an attacker-controlled tenant can never assert a verified
|
|
* email it does not own.
|
|
*/
|
|
export function deriveMicrosoftEmailVerified(
|
|
claims: Record<string, unknown>,
|
|
email: string
|
|
): boolean {
|
|
if (claims.email_verified !== undefined) {
|
|
return Boolean(claims.email_verified)
|
|
}
|
|
const { verified_primary_email: verifiedPrimary, verified_secondary_email: verifiedSecondary } =
|
|
claims
|
|
return (
|
|
(Array.isArray(verifiedPrimary) && verifiedPrimary.includes(email)) ||
|
|
(Array.isArray(verifiedSecondary) && verifiedSecondary.includes(email))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
export 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`)
|
|
}
|
|
|
|
let payload: Record<string, unknown>
|
|
try {
|
|
payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'))
|
|
} catch {
|
|
throw new Error(`Microsoft ${providerId} OAuth: failed to decode ID token payload`)
|
|
}
|
|
|
|
const email =
|
|
(payload.email as string) || (payload.preferred_username as string) || (payload.upn as string)
|
|
if (!email) {
|
|
throw new Error(
|
|
`Microsoft ${providerId} OAuth: ID token contains no email, preferred_username, or upn claim`
|
|
)
|
|
}
|
|
|
|
const emailVerified = deriveMicrosoftEmailVerified(payload, email)
|
|
|
|
const now = new Date()
|
|
return {
|
|
id: `${payload.oid || payload.sub}-${generateId()}`,
|
|
name: (payload.name as string) || 'Microsoft User',
|
|
email,
|
|
emailVerified,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
}
|
|
}
|