Skip to content

Commit

Permalink
Refactor to simplify state/effects, check guild membership
Browse files Browse the repository at this point in the history
  • Loading branch information
vcarl committed May 1, 2024
1 parent 1ceac21 commit cd347e9
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 63 deletions.
29 changes: 21 additions & 8 deletions netlify/functions/discordIdentity.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
139 changes: 87 additions & 52 deletions src/components/DiscordAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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<State> => {
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<State>("uninit");
const [stored, setStored] = React.useState<StoredToken | StoredTokenError>();

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{" "}
<a href="https://support.discord.com/hc/en-us/articles/213219267-Resending-Verification-Email">
verify your email
</a>{" "}
and try again.
</>
);
case "needsAuth":
default:
return (
<div>
<Button
onClick={() => window.open("/.netlify/functions/discordAuth")}
>
Auth
</Button>
</div>
);
}
return (
<>
{(() => {
switch (state) {
case "ok":
return children;
case "uninit":
return "checking auth…";
case "notMember":
return (
<div>
You’re not a member of Reactiflux!{" "}
<a href="https://discord.gg/reactiflux">Join us</a> if you like
💁
</div>
);
case "needsVerify":
return (
<div>
You don’t have a verified email associated with it. Please{" "}
<a href="https://support.discord.com/hc/en-us/articles/213219267-Resending-Verification-Email">
verify your email
</a>{" "}
and try again.
</div>
);
case "needsAuth":
default:
return (
<div>
<p>
Hi! This is a community job board for members of Reactiflux,
the largest chat community of professional React developers.
</p>
<p>
Since we’re a Discord community, we require that you sign in
so we can verify that you’re a member of the community.
</p>
<Button
onClick={() => window.open("/.netlify/functions/discordAuth")}
>
Sign in with Discord
</Button>
<p>
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.
</p>
</div>
);
}
})()}
</>
);
};
6 changes: 3 additions & 3 deletions src/pages/auth/discordcb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}),
);
Expand Down

0 comments on commit cd347e9

Please sign in to comment.