import type { AuthRequest, OAuthHelpers } from "@cloudflare/workers-oauth-provider";
import { Hono } from "hono";
import type { Props, GoogleUser } from "./props";
const app = new Hono<{
Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers };
}>();
app.get("/authorize", async (c) => {
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
if (!oauthReqInfo.clientId) {
return c.text("Invalid request", 400);
}
const googleAuthUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth");
googleAuthUrl.searchParams.set("client_id", c.env.GOOGLE_CLIENT_ID);
googleAuthUrl.searchParams.set("redirect_uri", new URL("/callback", c.req.url).href);
googleAuthUrl.searchParams.set("response_type", "code");
googleAuthUrl.searchParams.set("scope", "openid email profile");
googleAuthUrl.searchParams.set("state", btoa(JSON.stringify(oauthReqInfo)));
googleAuthUrl.searchParams.set("access_type", "offline");
googleAuthUrl.searchParams.set("prompt", "consent");
return Response.redirect(googleAuthUrl.toString());
});
app.get("/callback", async (c) => {
// Get the oauthReqInfo from state
const oauthReqInfo = JSON.parse(atob(c.req.query("state") as string)) as AuthRequest;
if (!oauthReqInfo.clientId) {
return c.text("Invalid state", 400);
}
const code = c.req.query("code");
if (!code) {
return c.text("Missing code", 400);
}
// Exchange authorization code for tokens
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
code,
client_id: c.env.GOOGLE_CLIENT_ID,
client_secret: c.env.GOOGLE_CLIENT_SECRET,
redirect_uri: new URL("/callback", c.req.url).href,
grant_type: "authorization_code",
}),
});
if (!tokenResponse.ok) {
console.error("Token exchange error:", await tokenResponse.text());
return c.text("Failed to exchange authorization code", 400);
}
const tokens = await tokenResponse.json() as {
access_token: string;
refresh_token?: string;
scope: string;
token_type: string;
id_token: string;
expires_in: number;
};
// Get user info from Google's userinfo endpoint
const userInfoResponse = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
headers: {
Authorization: `Bearer ${tokens.access_token}`,
},
});
if (!userInfoResponse.ok) {
console.error("User info error:", await userInfoResponse.text());
return c.text("Failed to fetch user information", 400);
}
try {
const userInfo = await userInfoResponse.json() as {
id: string;
email: string;
verified_email: boolean;
name: string;
given_name?: string;
family_name?: string;
picture?: string;
locale?: string;
};
// Example: restrict by domain
if (!userInfo.email.endsWith("@gotracksuit.com")) {
return c.text("Unauthorized domain", 403);
}
const user: GoogleUser = {
id: userInfo.id,
email: userInfo.email,
verified_email: userInfo.verified_email,
name: userInfo.name,
given_name: userInfo.given_name,
family_name: userInfo.family_name,
picture: userInfo.picture,
locale: userInfo.locale,
};
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: user.id,
metadata: {},
scope: tokens.scope.split(" "),
// This will be available on this.props inside MyMCP
props: {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
scope: tokens.scope.split(" "),
user,
} satisfies Props,
});
return Response.redirect(redirectTo);
} catch (error) {
console.error("Token verification error:", error);
console.error("Error details:", error instanceof Error ? error.message : String(error));
return c.text(`Failed to verify ID token: ${error instanceof Error ? error.message : String(error)}`, 400);
}
});
export const MyAuthHandler = app;