mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix: removed multiple account oauth functionality, updated better-auth to acknowledge security vulnerability
This commit is contained in:
@@ -5,13 +5,14 @@ import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { OAuthService } from '@/lib/oauth'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
import { account, user } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('OAuthConnectionsAPI')
|
||||
|
||||
interface GoogleIdToken {
|
||||
email?: string
|
||||
sub?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
// Valid OAuth providers
|
||||
@@ -36,30 +37,61 @@ export async function GET(request: NextRequest) {
|
||||
// Get all accounts for this user
|
||||
const accounts = await db.select().from(account).where(eq(account.userId, session.user.id))
|
||||
|
||||
// Get the user's email for fallback
|
||||
const userRecord = await db
|
||||
.select({ email: user.email })
|
||||
.from(user)
|
||||
.where(eq(user.id, session.user.id))
|
||||
.limit(1)
|
||||
|
||||
const userEmail = userRecord.length > 0 ? userRecord[0].email : null
|
||||
|
||||
// Process accounts to determine connections
|
||||
const connections: any[] = []
|
||||
|
||||
accounts.forEach((acc) => {
|
||||
for (const acc of accounts) {
|
||||
// Extract the base provider and feature type from providerId (e.g., 'google-email' -> 'google', 'email')
|
||||
const [provider, featureType = 'default'] = acc.providerId.split('-')
|
||||
|
||||
if (provider && VALID_PROVIDERS.includes(provider)) {
|
||||
// Get the account name (try to get email for Google accounts)
|
||||
let name = acc.accountId
|
||||
if (provider === 'google' && acc.idToken) {
|
||||
// Try multiple methods to get a user-friendly display name
|
||||
let displayName = ''
|
||||
|
||||
// Method 1: Try to extract email from ID token (works for Google, etc.)
|
||||
if (acc.idToken) {
|
||||
try {
|
||||
const decoded = jwtDecode<GoogleIdToken>(acc.idToken)
|
||||
if (decoded.email) {
|
||||
name = decoded.email
|
||||
displayName = decoded.email
|
||||
} else if (decoded.name) {
|
||||
displayName = decoded.name
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Error decoding Google ID token`, { accountId: acc.id })
|
||||
logger.warn(`[${requestId}] Error decoding ID token`, { accountId: acc.id })
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: For GitHub, the accountId might be the username
|
||||
if (!displayName && provider === 'github') {
|
||||
displayName = `${acc.accountId} (GitHub)`
|
||||
}
|
||||
|
||||
// Method 3: Use the user's email from our database
|
||||
if (!displayName && userEmail) {
|
||||
displayName = userEmail
|
||||
}
|
||||
|
||||
// Fallback: Use accountId with provider type as context
|
||||
if (!displayName) {
|
||||
displayName = `${acc.accountId} (${provider})`
|
||||
}
|
||||
|
||||
// Find existing connection for this provider and feature type
|
||||
// Create a unique connection key that includes the full provider ID
|
||||
const connectionKey = acc.providerId
|
||||
|
||||
// Find existing connection for this specific provider ID
|
||||
const existingConnection = connections.find(
|
||||
(conn) => conn.provider === provider && conn.featureType === featureType
|
||||
(conn) => conn.provider === connectionKey
|
||||
)
|
||||
|
||||
if (existingConnection) {
|
||||
@@ -67,12 +99,13 @@ export async function GET(request: NextRequest) {
|
||||
existingConnection.accounts = existingConnection.accounts || []
|
||||
existingConnection.accounts.push({
|
||||
id: acc.id,
|
||||
name,
|
||||
name: displayName,
|
||||
})
|
||||
} else {
|
||||
// Create new connection
|
||||
connections.push({
|
||||
provider: provider as OAuthService,
|
||||
provider: connectionKey,
|
||||
baseProvider: provider,
|
||||
featureType,
|
||||
isConnected: true,
|
||||
scopes: acc.scope ? acc.scope.split(' ') : [],
|
||||
@@ -80,13 +113,13 @@ export async function GET(request: NextRequest) {
|
||||
accounts: [
|
||||
{
|
||||
id: acc.id,
|
||||
name,
|
||||
name: displayName,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ connections }, { status: 200 })
|
||||
} catch (error) {
|
||||
|
||||
@@ -6,13 +6,14 @@ import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { parseProvider } from '@/lib/oauth'
|
||||
import { OAuthService } from '@/lib/oauth'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
import { account, user } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('OAuthCredentialsAPI')
|
||||
|
||||
interface GoogleIdToken {
|
||||
email?: string
|
||||
sub?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,22 +56,53 @@ export async function GET(request: NextRequest) {
|
||||
// Extract the feature type from providerId (e.g., 'google-default' -> 'default')
|
||||
const [_, featureType = 'default'] = acc.providerId.split('-')
|
||||
|
||||
// For Google accounts, try to get the email from the ID token
|
||||
let name = acc.accountId
|
||||
if (baseProvider === 'google' && acc.idToken) {
|
||||
// Try multiple methods to get a user-friendly display name
|
||||
let displayName = ''
|
||||
|
||||
// Method 1: Try to extract email from ID token (works for Google, etc.)
|
||||
if (acc.idToken) {
|
||||
try {
|
||||
const decoded = jwtDecode<GoogleIdToken>(acc.idToken)
|
||||
if (decoded.email) {
|
||||
name = decoded.email
|
||||
displayName = decoded.email
|
||||
} else if (decoded.name) {
|
||||
displayName = decoded.name
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Error decoding Google ID token`, { accountId: acc.id })
|
||||
logger.warn(`[${requestId}] Error decoding ID token`, { accountId: acc.id })
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: For GitHub, the accountId might be the username
|
||||
if (!displayName && baseProvider === 'github') {
|
||||
displayName = `${acc.accountId} (GitHub)`
|
||||
}
|
||||
|
||||
// Method 3: Try to get the user's email from our database
|
||||
if (!displayName) {
|
||||
try {
|
||||
const userRecord = await db
|
||||
.select({ email: user.email })
|
||||
.from(user)
|
||||
.where(eq(user.id, acc.userId))
|
||||
.limit(1)
|
||||
|
||||
if (userRecord.length > 0) {
|
||||
displayName = userRecord[0].email
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Error fetching user email`, { userId: acc.userId })
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Use accountId with provider type as context
|
||||
if (!displayName) {
|
||||
displayName = `${acc.accountId} (${baseProvider})`
|
||||
}
|
||||
|
||||
return {
|
||||
id: acc.id,
|
||||
name,
|
||||
name: displayName,
|
||||
provider,
|
||||
lastUsed: acc.updatedAt.toISOString(),
|
||||
isDefault: featureType === 'default',
|
||||
|
||||
@@ -505,7 +505,7 @@ export function FileSelector({
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
{/* Add another account option */}
|
||||
{/* Add another account option
|
||||
{credentials.length > 0 && (
|
||||
<CommandGroup>
|
||||
<CommandItem onSelect={handleAddCredential}>
|
||||
@@ -514,7 +514,7 @@ export function FileSelector({
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
)} */}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -412,7 +412,7 @@ export function FolderSelector({
|
||||
)}
|
||||
|
||||
{/* Add another account option */}
|
||||
{credentials.length > 0 && (
|
||||
{/* {credentials.length > 0 && (
|
||||
<CommandGroup>
|
||||
<CommandItem onSelect={handleAddCredential}>
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
@@ -420,7 +420,7 @@ export function FolderSelector({
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
)} */}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -128,8 +128,12 @@ export function OAuthRequiredModal({
|
||||
// Close the modal
|
||||
onClose()
|
||||
|
||||
// Begin OAuth flow with the appropriate provider
|
||||
await client.signIn.oauth2({
|
||||
logger.info('Linking OAuth2:', {
|
||||
providerId,
|
||||
requiredScopes,
|
||||
})
|
||||
|
||||
await client.oauth2.link({
|
||||
providerId,
|
||||
callbackURL: window.location.href,
|
||||
})
|
||||
|
||||
@@ -6,5 +6,4 @@ export const client = createAuthClient({
|
||||
})
|
||||
export const { useSession } = client
|
||||
|
||||
// Export commonly used hooks and methods
|
||||
export const { signIn, signUp, signOut } = client
|
||||
|
||||
@@ -27,6 +27,13 @@ export const auth = betterAuth({
|
||||
maxAge: 5 * 60, // Cache duration (5 minutes)
|
||||
},
|
||||
},
|
||||
account: {
|
||||
accountLinking: {
|
||||
enabled: true,
|
||||
allowDifferentEmails: true,
|
||||
trustedProviders: ["google", "github", "email-password"],
|
||||
},
|
||||
},
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID as string,
|
||||
@@ -120,6 +127,7 @@ export const auth = betterAuth({
|
||||
tokenUrl: 'https://github.com/login/oauth/access_token',
|
||||
userInfoUrl: 'https://api.github.com/user',
|
||||
scopes: ['user:email', 'repo'],
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/github-repo`,
|
||||
},
|
||||
{
|
||||
providerId: 'github-workflow',
|
||||
@@ -131,6 +139,7 @@ export const auth = betterAuth({
|
||||
userInfoUrl: 'https://api.github.com/user',
|
||||
scopes: ['workflow', 'repo'],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/github-workflow`,
|
||||
},
|
||||
|
||||
// Google providers for different purposes
|
||||
@@ -148,6 +157,7 @@ export const auth = betterAuth({
|
||||
'https://www.googleapis.com/auth/gmail.labels',
|
||||
],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-email`,
|
||||
},
|
||||
{
|
||||
providerId: 'google-calendar',
|
||||
@@ -161,6 +171,7 @@ export const auth = betterAuth({
|
||||
'https://www.googleapis.com/auth/calendar',
|
||||
],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-calendar`,
|
||||
},
|
||||
{
|
||||
providerId: 'google-drive',
|
||||
@@ -175,6 +186,7 @@ export const auth = betterAuth({
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-drive`,
|
||||
},
|
||||
{
|
||||
providerId: 'google-docs',
|
||||
@@ -189,6 +201,7 @@ export const auth = betterAuth({
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-docs`,
|
||||
},
|
||||
{
|
||||
providerId: 'google-sheets',
|
||||
@@ -203,6 +216,7 @@ export const auth = betterAuth({
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-sheets`,
|
||||
},
|
||||
|
||||
// Supabase provider
|
||||
@@ -217,6 +231,7 @@ export const auth = betterAuth({
|
||||
scopes: ['database.read', 'database.write', 'projects.read'],
|
||||
responseType: 'code',
|
||||
pkce: true,
|
||||
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/supabase`,
|
||||
},
|
||||
|
||||
// X provider
|
||||
|
||||
+1
-17
@@ -13,7 +13,6 @@ import {
|
||||
} from '@/components/ui/command'
|
||||
import { OAuthRequiredModal } from '@/components/ui/oauth-required-modal'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
Credential,
|
||||
@@ -106,7 +105,7 @@ export function CredentialSelector({
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [provider, onChange, selectedId])
|
||||
}, [provider, onChange, selectedId, getProviderId])
|
||||
|
||||
// Fetch credentials on initial mount and when dependencies change
|
||||
useEffect(() => {
|
||||
@@ -154,21 +153,6 @@ export function CredentialSelector({
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
// Handle direct OAuth flow
|
||||
const handleDirectOAuth = async () => {
|
||||
try {
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Begin OAuth flow with the appropriate provider
|
||||
await client.signIn.oauth2({
|
||||
providerId,
|
||||
callbackURL: window.location.href,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('OAuth login error:', { error })
|
||||
}
|
||||
}
|
||||
|
||||
// Get provider icon
|
||||
const getProviderIcon = (providerName: OAuthProvider) => {
|
||||
const { baseProvider } = parseProvider(providerName)
|
||||
|
||||
+40
-10
@@ -72,12 +72,13 @@ export function Credentials({ onOpenChange }: CredentialsProps) {
|
||||
|
||||
// Update services with connection status and account info
|
||||
const updatedServices = serviceDefinitions.map((service) => {
|
||||
// Find matching connection
|
||||
// Find matching connection - now we can do an exact match on providerId
|
||||
const connection = connections.find((conn: any) => {
|
||||
const [provider, featureType] = conn.provider.split('-')
|
||||
return service.providerId.startsWith(provider)
|
||||
})
|
||||
// Exact match on providerId is the most reliable
|
||||
return conn.provider === service.providerId;
|
||||
});
|
||||
|
||||
// If we found an exact match, use it
|
||||
if (connection) {
|
||||
return {
|
||||
...service,
|
||||
@@ -87,7 +88,31 @@ export function Credentials({ onOpenChange }: CredentialsProps) {
|
||||
}
|
||||
}
|
||||
|
||||
return service
|
||||
// If no exact match, check if any connection has all the required scopes
|
||||
const connectionWithScopes = connections.find((conn: any) => {
|
||||
// Only consider connections from the same base provider
|
||||
if (!conn.baseProvider || !service.providerId.startsWith(conn.baseProvider)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if all required scopes for this service are included in the connection
|
||||
if (conn.scopes && service.scopes) {
|
||||
return service.scopes.every(scope => conn.scopes.includes(scope));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (connectionWithScopes) {
|
||||
return {
|
||||
...service,
|
||||
isConnected: connectionWithScopes.accounts?.length > 0,
|
||||
accounts: connectionWithScopes.accounts || [],
|
||||
lastConnected: connectionWithScopes.lastConnected,
|
||||
}
|
||||
}
|
||||
|
||||
return service;
|
||||
})
|
||||
|
||||
setServices(updatedServices)
|
||||
@@ -184,14 +209,19 @@ export function Credentials({ onOpenChange }: CredentialsProps) {
|
||||
saveToStorage<string[]>('pending_oauth_scopes', service.scopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', service.providerId)
|
||||
|
||||
logger.info('Connecting service:', {
|
||||
serviceId: service.id,
|
||||
providerId: service.providerId,
|
||||
scopes: service.scopes,
|
||||
})
|
||||
|
||||
// Begin OAuth flow with the appropriate provider
|
||||
await client.signIn.oauth2({
|
||||
await client.oauth2.link({
|
||||
providerId: service.providerId,
|
||||
callbackURL: window.location.href,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('OAuth login error:', { error })
|
||||
logger.error('OAuth connection error:', { error })
|
||||
setIsConnecting(null)
|
||||
}
|
||||
}
|
||||
@@ -359,7 +389,7 @@ export function Credentials({ onOpenChange }: CredentialsProps) {
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
{/* <Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full mt-2"
|
||||
@@ -377,7 +407,7 @@ export function Credentials({ onOpenChange }: CredentialsProps) {
|
||||
Connect Another Account
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+13
-4
@@ -1,9 +1,8 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getSessionCookie } from 'better-auth'
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
// Check if the path is exactly /w
|
||||
if (request.nextUrl.pathname === '/w') {
|
||||
// Check if the path is exactly /w
|
||||
if (request.nextUrl.pathname === '/w') {
|
||||
return NextResponse.redirect(new URL('/w/1', request.url))
|
||||
}
|
||||
|
||||
@@ -12,8 +11,18 @@ export async function middleware(request: NextRequest) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
const cookieHeader = request.headers.get("cookie");
|
||||
const cookies = cookieHeader?.split("; ").reduce((acc, cookie) => {
|
||||
const [key, value] = cookie.split("=");
|
||||
acc.set(key, value);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
const sessionCookie =
|
||||
cookies?.get("better-auth.session_token") ||
|
||||
cookies?.get("__Secure-better-auth.session_token");
|
||||
|
||||
// Existing auth check for protected routes
|
||||
const sessionCookie = getSessionCookie(request)
|
||||
if (!sessionCookie) {
|
||||
return NextResponse.redirect(new URL('/login', request.url))
|
||||
}
|
||||
|
||||
Generated
+41
-10
@@ -32,7 +32,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"@webcontainer/api": "^1.5.1-internal.9",
|
||||
"better-auth": "^1.1.18",
|
||||
"better-auth": "^1.2.4",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -597,7 +597,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@better-fetch/fetch": {
|
||||
"version": "1.1.12"
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.15.tgz",
|
||||
"integrity": "sha512-0Bl8YYj1f8qCTNHeSn5+1DWv2hy7rLBrQ8rS8Y9XYloiwZEfc3k4yspIG0llRxafxqhGCwlGRg+F8q1HZRCMXA=="
|
||||
},
|
||||
"node_modules/@cerebras/cerebras_cloud_sdk": {
|
||||
"version": "1.23.0",
|
||||
@@ -3939,29 +3941,34 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/better-auth": {
|
||||
"version": "1.1.18",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.2.4.tgz",
|
||||
"integrity": "sha512-/ZK2jbUjm8JwdeCLFrUWUBmexPyI9PkaLVXWLWtN60sMDHTY8B5G72wcHglo1QMFBaw4G0qFkP5ayl9k6XfDaA==",
|
||||
"dependencies": {
|
||||
"@better-auth/utils": "0.2.3",
|
||||
"@better-fetch/fetch": "1.1.12",
|
||||
"@better-fetch/fetch": "^1.1.15",
|
||||
"@noble/ciphers": "^0.6.0",
|
||||
"@noble/hashes": "^1.6.1",
|
||||
"@simplewebauthn/browser": "^13.0.0",
|
||||
"@simplewebauthn/server": "^13.0.0",
|
||||
"better-call": "0.3.3",
|
||||
"better-call": "^1.0.3",
|
||||
"defu": "^6.1.4",
|
||||
"jose": "^5.9.6",
|
||||
"kysely": "^0.27.4",
|
||||
"kysely": "^0.27.6",
|
||||
"nanostores": "^0.11.3",
|
||||
"valibot": "1.0.0-beta.15",
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
},
|
||||
"node_modules/better-call": {
|
||||
"version": "0.3.3",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.4.tgz",
|
||||
"integrity": "sha512-NdAihYdkS0IOz1mtz8mw1gWacCxR9r921U8YqB+VB6++rt8edMG13vVL16Y4TBL4XkjMK/DUewEsOOFkw9LJYQ==",
|
||||
"dependencies": {
|
||||
"@better-fetch/fetch": "^1.1.4",
|
||||
"rou3": "^0.5.1",
|
||||
"uncrypto": "^0.1.3",
|
||||
"zod": "^3.24.1"
|
||||
"set-cookie-parser": "^2.7.1",
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
@@ -7294,7 +7301,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/kysely": {
|
||||
"version": "0.27.5",
|
||||
"version": "0.27.6",
|
||||
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.6.tgz",
|
||||
"integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -9008,6 +9017,8 @@
|
||||
},
|
||||
"node_modules/rou3": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
|
||||
"integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
@@ -9150,6 +9161,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"hasInstallScript": true,
|
||||
@@ -9985,6 +10002,20 @@
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/valibot": {
|
||||
"version": "1.0.0-beta.15",
|
||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.15.tgz",
|
||||
"integrity": "sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"@webcontainer/api": "^1.5.1-internal.9",
|
||||
"better-auth": "^1.1.18",
|
||||
"better-auth": "^1.2.4",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
Reference in New Issue
Block a user