Created
May 27, 2022 16:31
-
-
Save angezanetti/c5e67900974bb840c1b74d37abc59e09 to your computer and use it in GitHub Desktop.
Twitter auth with sveltekit & supabase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import supabase from '$lib/db'; | |
import * as cookie from 'cookie'; | |
import { TwitterApi } from 'twitter-api-v2'; | |
const twitterClient = new TwitterApi({ | |
appKey: 'xxx', | |
appSecret: 'xxxxx' | |
}); | |
// GET REQUEST | |
export async function get({ params, url }) { | |
const path = params.auth; | |
switch (path) { | |
case 'auth/twitter/login': | |
return await handleTwitterLogin(); | |
case 'auth/twitter/callback': | |
return await handleTwitterCallback(params, url); | |
case 'auth/logout': | |
return await handleTwitterLogout(url); | |
default: | |
return { status: 404 }; | |
} | |
} | |
function twitterUserClient(accessToken, accessSecret) { | |
return new TwitterApi({ | |
appKey: 'xxxx', | |
appSecret: 'xxxxx', | |
accessToken, | |
accessSecret | |
}); | |
} | |
/** | |
* Generates Twitter authentication URL and temporarily stores `oauth_token` in database. | |
*/ | |
async function handleTwitterLogin() { | |
let authLink; | |
try { | |
authLink = await twitterClient.generateAuthLink( | |
`https://xxxx.app/api/auth/twitter/callback` | |
); | |
} catch (e) { | |
console.error(e); | |
return { | |
status: 303, | |
headers: { | |
location: '/' | |
} | |
}; | |
} | |
if (!authLink?.url || !authLink?.oauth_token || !authLink?.oauth_token_secret) { | |
return { status: 500 }; | |
} | |
const { data, error } = await supabase | |
.from('tokens') | |
.insert({ | |
redirect: authLink.url, | |
oauth_token: authLink.oauth_token, | |
oauth_token_secret: authLink.oauth_token_secret | |
}) | |
.limit(1) | |
.single(); | |
if (error || !data?.id) { | |
return { status: 500 }; | |
} | |
return { | |
status: 303, | |
headers: { | |
location: authLink.url | |
} | |
}; | |
} | |
/** | |
* Handles Twitters authentication callback, fetches permanent access-tokens, | |
* and saves them to the database. | |
*/ | |
async function handleTwitterCallback(params, url) { | |
const oauth_token = url.searchParams.get('oauth_token'); | |
const oauth_verifier = url.searchParams.get('oauth_verifier'); | |
if (!oauth_token || !oauth_verifier) { | |
console.error("No 'oauth_token' in callback request"); | |
return { | |
status: 303, | |
headers: { | |
location: '/' | |
} | |
}; | |
} | |
const { data: token, error } = await supabase | |
.from('tokens') | |
.select() | |
.eq('oauth_token', oauth_token) | |
.limit(1) | |
.single(); | |
const oauth_token_secret = token['oauth_token_secret']; | |
if (error || !oauth_token_secret) { | |
console.error("Error while accessing database or 'oauth_token_secret' not found", error); | |
return { | |
status: 303, | |
headers: { | |
location: '/' | |
} | |
}; | |
} | |
try { | |
// Create twitter user-client from temporary tokens | |
const twitterClient = twitterUserClient(oauth_token, oauth_token_secret); | |
const { accessToken: access_token, accessSecret: access_secret } = await twitterClient.login( | |
oauth_verifier | |
); | |
// Delete all existing with duplicate tokens | |
await supabase | |
.from('tokens') | |
.delete() | |
.neq('id', token.id) | |
.match({ access_token, access_secret }); | |
// Sync User-Profile with authorized twitter-user client | |
const profile = await syncTwitterProfileDetails(access_token, access_secret); | |
// Update with permanent token, delete temporary tokens | |
const { data: newToken, error } = await supabase | |
.from('tokens') | |
.update({ | |
user_id: profile['id'], | |
access_token, | |
access_secret, | |
oauth_token: null, | |
oauth_token_secret: null, | |
twitter_id: profile['twitter_user_id'] | |
}) | |
.eq('oauth_token', oauth_token) | |
.limit(1) | |
.single(); | |
if (error || !newToken?.id) { | |
console.error('Error while updating access-tokens', error); | |
return { | |
status: 303, | |
headers: { | |
location: '/' | |
} | |
}; | |
} | |
// Successfully authenticated, redirecting… | |
return { | |
status: 303, | |
headers: { | |
'Set-Cookie': cookie.serialize('user_id', profile['id'], { | |
httpOnly: true, | |
sameSite: 'lax', | |
maxAge: 60 * 60 * 24 * 7, | |
path: '/' | |
}), | |
location: '/' | |
} | |
}; | |
} catch (e) { | |
console.error('Error while logging in with twitter user-client', e); | |
} | |
} | |
/** | |
* Handles logout of twitter user and removes token from the database and cookie | |
*/ | |
export const handleTwitterLogout = async (url) => { | |
const user = url.searchParams.get('user'); | |
console.log(user); | |
const { error } = await supabase.from('tokens').delete().eq('user_id', user); | |
if (error) console.error(`Error while deleting token with id '${user}' on logout`, error); | |
return { | |
status: 303, | |
headers: { | |
'set-cookie': 'user_id=""; path=/; HttpOnly; expires=Thu, 01 Jan 1970 00:00:00 GMT', | |
location: '/' | |
} | |
}; | |
}; | |
/** | |
* If User has successfully authenticated twitter user, it's | |
* Twitter-related profile information is updated automatically. | |
*/ | |
export const syncTwitterProfileDetails = async (twitterAccessToken, twitterAccessSecret) => { | |
if (!twitterAccessToken || !twitterAccessSecret) return; | |
const twitterClient = await twitterUserClient(twitterAccessToken, twitterAccessSecret); | |
const twitterUser = await twitterClient.v1.verifyCredentials({ include_email: true }); | |
const attributesExist = | |
twitterUser?.id_str && | |
twitterUser?.email && | |
twitterUser?.screen_name && | |
twitterUser?.name && | |
twitterUser?.followers_count && | |
twitterUser?.profile_image_url_https; | |
if (!attributesExist) return; | |
await supabase.from('user_profile').delete().eq('twitter_user_id', twitterUser.id_str); | |
const { data: profile, error } = await supabase.from('user_profile').insert({ | |
twitter_user_id: twitterUser.id_str, | |
full_name: twitterUser.name, | |
email: twitterUser.email, | |
user_name: twitterUser.screen_name, | |
followers_count: twitterUser.followers_count, | |
avatar_url: twitterUser.profile_image_url_https | |
}); | |
if (error || !profile) { | |
console.error('Error while syncing profile with twitter data', error); | |
} | |
return profile[0]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment