diff --git a/netlify/functions/discordIdentity.mts b/netlify/functions/discordIdentity.mts index d98392a..c3d73b5 100644 --- a/netlify/functions/discordIdentity.mts +++ b/netlify/functions/discordIdentity.mts @@ -5,14 +5,27 @@ const handler = async (request: Request, context: Context) => { // header is present on an incoming request. So, hack const Authorization = request.headers.get("x-auth")!; try { - const res = await fetch("https://discord.com/api/users/@me", { - headers: { Authorization }, - }); - // Why the heck can't I just return the result of `fetch()` - // Produced weird content decode errors but everything seemed fine - return new Response(await res.text(), { - headers: { "Content-Type": "application/json" }, - }); + const [userRes, guildsRes] = await Promise.all([ + fetch("https://discord.com/api/users/@me", { + headers: { Authorization }, + }), + fetch("https://discord.com/api/users/@me/guilds", { + headers: { Authorization }, + }), + ]); + const [user, guilds] = await Promise.all([ + userRes.json(), + guildsRes.json(), + ]); + return new Response( + JSON.stringify({ + user, + isMember: guilds.some((g) => g.id === "102860784329052160"), + }), + { + headers: { "Content-Type": "application/json" }, + }, + ); } catch (e) { console.error("[DIS_ID]", e); return new Response(JSON.stringify(e), { status: 400 }); diff --git a/src/components/DiscordAuth.tsx b/src/components/DiscordAuth.tsx index 7aaba87..d305d80 100644 --- a/src/components/DiscordAuth.tsx +++ b/src/components/DiscordAuth.tsx @@ -15,9 +15,12 @@ interface StoredTokenError { message: string; raw: Error; } -interface DiscordUser { - email: string; - verified: boolean; +interface DiscordIdentity { + isMember: boolean; + user: { + email: string; + verified: boolean; + }; } const retrieveStoredToken = (): StoredToken | StoredTokenError | undefined => { @@ -26,70 +29,102 @@ const retrieveStoredToken = (): StoredToken | StoredTokenError | undefined => { return undefined; }; -type State = "uninit" | "needsAuth" | "needsVerify" | "ok"; +type State = + | "uninit" + | "needsAuth" + | "needsVerify" + | "notMember" + | "ok" + | "err"; + +const checkAuth = async ( + stored?: StoredToken | StoredTokenError, +): Promise => { + if (!stored || stored.state !== "ok") return "needsAuth"; + const res = await fetch("/.netlify/functions/discordIdentity", { + headers: { "x-auth": stored.token }, + }); + const loadedUser = (await res.json()) as DiscordIdentity; + if (!loadedUser.user.email || !loadedUser.user.verified) { + return "needsVerify"; + } + if (!loadedUser.isMember) { + return "notMember"; + } + return "ok"; +}; export const DiscordAuth = ({ children }: Props) => { const [state, setState] = React.useState("uninit"); - const [stored, setStored] = React.useState(); React.useEffect(() => { const storedToken = retrieveStoredToken(); if (storedToken) { - setStored(storedToken); + checkAuth(storedToken).then(setState); return; } - const onEvent = (e: StorageEvent) => { + const onEvent = async (e: StorageEvent) => { if (e.key !== "doa") return; - setStored(retrieveStoredToken()); + setState(await checkAuth(retrieveStoredToken())); }; window.addEventListener("storage", onEvent); setState("needsAuth"); return () => window.removeEventListener("storage", onEvent); }, []); - React.useEffect(() => { - if (!stored || stored.state !== "ok") return; - (async () => { - console.log("fetching"); - const res = await fetch("/.netlify/functions/discordIdentity", { - headers: { "x-auth": stored.token }, - }); - const loadedUser = (await res.json()) as DiscordUser; - if (!loadedUser.email || !loadedUser.verified) { - setState("needsVerify"); - } else { - setState("ok"); - } - })(); - }, [stored]); - - switch (state) { - case "ok": - return children; - case "uninit": - return "checking auth…"; - case "needsVerify": - return ( - <> - This Discord account does not have a verified email associated with - it. Please{" "} - - verify your email - {" "} - and try again. - - ); - case "needsAuth": - default: - return ( -
- -
- ); - } + return ( + <> + {(() => { + switch (state) { + case "ok": + return children; + case "uninit": + return "checking auth…"; + case "notMember": + return ( +
+ You’re not a member of Reactiflux!{" "} + Join us if you like + 💁 +
+ ); + case "needsVerify": + return ( +
+ You don’t have a verified email associated with it. Please{" "} + + verify your email + {" "} + and try again. +
+ ); + case "needsAuth": + default: + return ( +
+

+ Hi! This is a community job board for members of Reactiflux, + the largest chat community of professional React developers. +

+

+ Since we’re a Discord community, we require that you sign in + so we can verify that you’re a member of the community. +

+ +

+ We’ll ask for permission to read your email and guild list, we + need those to confirm you have a verified email associated + with the account and that you’re a member of Reactiflux. +

+
+ ); + } + })()} + + ); }; diff --git a/src/pages/auth/discordcb.tsx b/src/pages/auth/discordcb.tsx index f7a5489..15b419e 100644 --- a/src/pages/auth/discordcb.tsx +++ b/src/pages/auth/discordcb.tsx @@ -13,12 +13,12 @@ const DiscordCB = () => { }, }) .then(async (res) => { - const user = await res.json(); - if (!user.email || !user.verified) { + const identity = await res.json(); + if (!identity.user.email || !identity.user.verified) { localStorage.setItem( "doa", JSON.stringify({ - state: "err", + state: "needsVerify", message: "This account does not have a verified email address", }), );