feat(oauth): added x oauth, needs to be fixed bc twitter doesn't respond with user's email

This commit is contained in:
Waleed Latif
2025-03-10 18:32:49 -07:00
parent aa26ee2eeb
commit b446a3ce51
10 changed files with 159 additions and 2173 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ interface GoogleIdToken {
}
// Valid OAuth providers
const VALID_PROVIDERS = ['google', 'github', 'twitter']
const VALID_PROVIDERS = ['google', 'github', 'x']
/**
* Get all OAuth connections for the current user
+4 -4
View File
@@ -88,10 +88,10 @@ export async function POST(request: NextRequest) {
clientId = process.env.GITHUB_CLIENT_ID
clientSecret = process.env.GITHUB_CLIENT_SECRET
break
case 'twitter':
tokenEndpoint = 'https://api.twitter.com/2/oauth2/token'
clientId = process.env.TWITTER_CLIENT_ID
clientSecret = process.env.TWITTER_CLIENT_SECRET
case 'x':
tokenEndpoint = 'https://api.x.com/2/oauth2/token'
clientId = process.env.X_CLIENT_ID
clientSecret = process.env.X_CLIENT_SECRET
break
default:
throw new Error(`Unsupported provider: ${provider}`)
+3 -2
View File
@@ -43,9 +43,10 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
repo: 'Access your repositories',
workflow: 'Manage repository workflows',
'user:email': 'Access your email address',
'tweet.read': 'Read your tweets',
'tweet.write': 'Create tweets on your behalf',
'tweet.read': 'Read your tweets and timeline',
'tweet.write': 'Post tweets on your behalf',
'users.read': 'Read your profile information',
'offline.access': 'Access your account when you are not using the application',
}
// Convert OAuth scope to user-friendly description
+36 -24
View File
@@ -38,11 +38,6 @@ export const auth = betterAuth({
'https://www.googleapis.com/auth/userinfo.profile',
],
},
// twitter: {
// clientId: process.env.TWITTER_CLIENT_ID as string,
// clientSecret: process.env.TWITTER_CLIENT_SECRET as string,
// scopes: ['tweet.read', 'users.read'],
// },
},
emailAndPassword: {
enabled: true,
@@ -193,25 +188,42 @@ export const auth = betterAuth({
pkce: true,
},
// Twitter providers
// {
// providerId: 'twitter-read',
// clientId: process.env.TWITTER_CLIENT_ID as string,
// clientSecret: process.env.TWITTER_CLIENT_SECRET as string,
// authorizationUrl: 'https://twitter.com/i/oauth2/authorize',
// tokenUrl: 'https://api.twitter.com/2/oauth2/token',
// userInfoUrl: 'https://api.twitter.com/2/users/me',
// scopes: ['tweet.read', 'users.read'],
// },
// {
// providerId: 'twitter-write',
// clientId: process.env.TWITTER_CLIENT_ID as string,
// clientSecret: process.env.TWITTER_CLIENT_SECRET as string,
// authorizationUrl: 'https://twitter.com/i/oauth2/authorize',
// tokenUrl: 'https://api.twitter.com/2/oauth2/token',
// userInfoUrl: 'https://api.twitter.com/2/users/me',
// scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
// },
// X provider
{
providerId: 'x',
clientId: process.env.X_CLIENT_ID as string,
clientSecret: process.env.X_CLIENT_SECRET as string,
authorizationUrl: 'https://x.com/i/oauth2/authorize',
tokenUrl: 'https://api.x.com/2/oauth2/token',
userInfoUrl: 'https://api.x.com/2/users/me',
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
pkce: true,
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/x`,
getUserInfo: async (tokens) => {
const response = await fetch(
'https://api.x.com/2/users/me?user.fields=profile_image_url',
{
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
}
)
const profile = await response.json()
const now = new Date()
return {
id: profile.data.id,
name: profile.data.name,
email: profile.data.username || null, // Use username as email
image: profile.data.profile_image_url,
emailVerified: profile.data.verified || false,
createdAt: now,
updatedAt: now,
}
},
},
],
}),
],
+4 -2
View File
@@ -135,11 +135,11 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
x: {
id: 'x',
name: 'X',
description: 'Post tweets and interact with the X API.',
description: 'Read and post tweets on X (formerly Twitter).',
providerId: 'x',
icon: (props) => xIcon(props),
baseProviderIcon: (props) => xIcon(props),
scopes: [],
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
},
},
defaultService: 'x',
@@ -207,6 +207,8 @@ export function getServiceIdFromScopes(provider: OAuthProvider, scopes: string[]
}
} else if (provider === 'supabase') {
return 'supabase'
} else if (provider === 'x') {
return 'x'
}
return providerConfig.defaultService
+106 -2135
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -37,7 +37,7 @@ export const readTool: ToolConfig<XReadParams, XReadResponse> = {
'attachments.poll_ids',
].join(',')
return `https://api.twitter.com/2/tweets/${params.tweetId}?expansions=${expansions}`
return `https://api.x.com/2/tweets/${params.tweetId}?expansions=${expansions}`
},
method: 'GET',
headers: (params) => ({
+1 -1
View File
@@ -63,7 +63,7 @@ export const searchTool: ToolConfig<XSearchParams, XSearchResponse> = {
if (params.endTime) queryParams.append('end_time', params.endTime)
if (params.sortOrder) queryParams.append('sort_order', params.sortOrder)
return `https://api.twitter.com/2/tweets/search/recent?${queryParams.toString()}`
return `https://api.x.com/2/tweets/search/recent?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
+2 -2
View File
@@ -33,7 +33,7 @@ export const userTool: ToolConfig<XUserParams, XUserResponse> = {
','
)
return `https://api.twitter.com/2/users/by/username/${username}?user.fields=${userFields}`
return `https://api.x.com/2/users/by/username/${username}?user.fields=${userFields}`
},
method: 'GET',
headers: (params) => ({
@@ -83,7 +83,7 @@ export const userTool: ToolConfig<XUserParams, XUserResponse> = {
// Fetch recent tweets if requested
if (includeRecentTweets && apiKey) {
const tweetsResponse = await fetch(
`https://api.twitter.com/2/users/${user.id}/tweets?max_results=10&tweet.fields=created_at,conversation_id,in_reply_to_user_id,attachments`,
`https://api.x.com/2/users/${user.id}/tweets?max_results=10&tweet.fields=created_at,conversation_id,in_reply_to_user_id,attachments`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
+1 -1
View File
@@ -37,7 +37,7 @@ export const writeTool: ToolConfig<XWriteParams, XWriteResponse> = {
},
request: {
url: 'https://api.twitter.com/2/tweets',
url: 'https://api.x.com/2/tweets',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,