Skip to content

Instantly share code, notes, and snippets.

@okikio
Created May 5, 2024 02:49
Show Gist options
  • Save okikio/e958e9a7572d68adf73d7d6450883b0c to your computer and use it in GitHub Desktop.
Save okikio/e958e9a7572d68adf73d7d6450883b0c to your computer and use it in GitHub Desktop.
Google OAuth2 w/ Deno & Hono
import { CLIENT_ID, CLIENT_SECRET } from "~/env.ts";
import { gmail_v1, auth } from "@googleapis/gmail";
import { Hono } from '@kyiro/hono';
import open from 'open';
const REDIRECT_URI = "http://localhost:8000/token";
const SCOPE = "https://www.googleapis.com/auth/gmail.readonly";
console.log({
SCOPE,
REDIRECT_URI,
CLIENT_ID,
CLIENT_SECRET,
})
// Setup the OAuth 2.0 client
const oauth2Client = new auth.OAuth2(
CLIENT_ID, // Client ID
CLIENT_SECRET, // Client Secret
REDIRECT_URI // Redirect URI set in the Google Cloud Console
);
// Generate authentication URL
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPE,
});
// Visit this URL to get authorization code.
console.log('Authorize this app by visiting this url:', authUrl);
const app = new Hono();
// Initialize Deno KV storage
const kv = await Deno.openKv();
// for await (const item of kv.list({ prefix: ["tokens"]})) {
// await kv.delete(item.key);
// }
app.get('/', async (c) => {
// Check if we already have a valid access token
const accessToken = await kv.get<string>(["tokens", "access_token"]);
if (accessToken.value) {
return c.redirect('/email');
}
// No valid access token, need to authenticate or refresh
const refreshToken = await kv.get<string>(["tokens", "refresh_token"]);
if (refreshToken.value) {
// Try to refresh the token
const tokens = await refreshAccessToken(refreshToken.value);
if (tokens?.access_token) {
await kv.set(["tokens", "access_token"], tokens?.access_token, {
expireIn: tokens.expiry_date! - Date.now(),
});
return c.redirect('/email');
}
}
await open(authUrl.toString()); // Opens the URL in the default browser
return c.text('Please authorize the app to access your Gmail account.');
});
app.get('/token', async (c) => {
const code = c.req.query("code");
if (code) {
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);
console.log({ tokens });
await kv.set(["tokens", "access_token"], tokens.access_token, {
expireIn: tokens.expiry_date! - Date.now(),
});
if (tokens.refresh_token) { // Store refresh token only if present
await kv.set(["tokens", "refresh_token"], tokens.refresh_token);
}
return c.redirect('/email');
}
return c.text('Failed to authenticate!');
});
app.get('/email', async (c) => {
console.time("Benchmark");
const accessToken = await kv.get<string>(["tokens", "access_token"]);
if (!accessToken.value) return c.redirect('/');
oauth2Client.setCredentials({ access_token: accessToken.value });
try {
const gmail = new gmail_v1.Gmail({ auth: oauth2Client });
const response = await gmail.users.messages.list({
userId: 'me',
labelIds: ['INBOX'],
maxResults: 10, // Fetches the latest 10 emails
});
const messages = response.data.messages;
console.log({
messages
})
if (messages && messages.length) {
const messageArr = await Promise.all(
messages.map(message =>
gmail.users.messages.get({
id: message.id,
userId: 'me',
})
)
);
return c.json(messageArr.map(msg => msg.data));
} else {
return c.text('No emails found.');
}
} catch (error) {
console.error('Failed to fetch emails:', error);
return c.text('Failed to fetch emails.');
} finally {
console.timeEnd("Benchmark");
}
});
async function refreshAccessToken(refreshToken: string) {
try {
const { tokens } = await oauth2Client.refreshToken(refreshToken);
return tokens;
} catch (error) {
console.error('Error refreshing access token:', error);
return null;
}
}
Deno?.serve?.(app.fetch);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment