Show dialog for failed login attempts

This commit is contained in:
Remmy Cat Stock 2022-10-18 23:17:37 +02:00
parent cf704c3ec4
commit ab98a106bb
No known key found for this signature in database
GPG Key ID: E3F847E89FAC01C7
8 changed files with 191 additions and 26 deletions

View File

@ -1,9 +1,10 @@
import { Link } from "@remix-run/react";
import { Link, useSearchParams } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { useUser } from "~/modules/auth";
import { LOG_IN_URL, LOG_OUT_URL, userPage } from "~/utils/urls";
import { Avatar } from "../Avatar";
import { Button } from "../Button";
import { Dialog } from "../Dialog";
import { DiscordIcon } from "../icons/Discord";
import { LogOutIcon } from "../icons/LogOut";
import { UserIcon } from "../icons/User";
@ -12,8 +13,9 @@ import { Popover } from "../Popover";
export function UserItem() {
const { t } = useTranslation();
const user = useUser();
const [searchParams, setSearchParams] = useSearchParams();
if (user)
if (user) {
return (
<Popover
buttonChildren={
@ -45,16 +47,56 @@ export function UserItem() {
</div>
</Popover>
);
}
const authError = searchParams.get("authError");
const closeAuthErrorDialog = () => {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.delete("authError");
setSearchParams(newSearchParams);
};
return (
<form action={LOG_IN_URL} method="post" data-cy="log-in-form">
<button
type="submit"
className="layout__log-in-button"
data-cy="log-in-button"
>
<DiscordIcon /> {t("header.login")}
</button>
</form>
<>
<form action={LOG_IN_URL} method="post" data-cy="log-in-form">
<button
type="submit"
className="layout__log-in-button"
data-cy="log-in-button"
>
<DiscordIcon /> {t("header.login")}
</button>
</form>
{authError != null && (
<Dialog isOpen close={closeAuthErrorDialog}>
<div className="stack md">
<AuthenticationErrorHelp errorCode={authError} />
<Button onClick={closeAuthErrorDialog}>{t("actions.close")}</Button>
</div>
</Dialog>
)}
</>
);
}
function AuthenticationErrorHelp({ errorCode }: { errorCode: string }) {
const { t } = useTranslation();
switch (errorCode) {
case "aborted":
return (
<>
<h2 className="text-lg text-center">{t("auth.errors.aborted")}</h2>
{t("auth.errors.discordPermissions")}
</>
);
case "unknown":
default:
return (
<>
<h2 className="text-lg text-center">{t("auth.errors.failed")}</h2>
{t("auth.errors.unknown")}
</>
);
}
}

View File

@ -0,0 +1 @@
export type AuthErrorCode = "aborted" | "unknown";

View File

@ -9,3 +9,5 @@ export {
export { getUser, requireUser } from "./user.server";
export { useUser } from "./user";
export { AuthErrorCode } from "./errors";

View File

@ -1,7 +1,7 @@
import type { ActionFunction, LoaderFunction } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { canPerformAdminActions } from "~/permissions";
import { ADMIN_PAGE } from "~/utils/urls";
import { ADMIN_PAGE, authErrorUrl } from "~/utils/urls";
import {
authenticator,
DISCORD_AUTH_KEY,
@ -11,11 +11,19 @@ import { authSessionStorage } from "./session.server";
import { getUser } from "./user.server";
export const callbackLoader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url);
if (url.searchParams.get("error") === "access_denied") {
// The user denied the authentication request
// This is part of the oauth2 protocol, but remix-auth-oauth2 doesn't do
// nice error handling for this case.
// https://www.oauth.com/oauth2-servers/server-side-apps/possible-errors/
return redirect(authErrorUrl("aborted"));
}
await authenticator.authenticate(DISCORD_AUTH_KEY, request, {
successRedirect: "/",
// TODO: should include query param that displays an error banner explaining that log in went wrong
// and where to get help for that
failureRedirect: "/",
failureRedirect: authErrorUrl("unknown"),
});
throw new Response("Unknown authentication state", { status: 500 });

View File

@ -10,6 +10,7 @@ import type {
SubWeaponId,
} from "~/modules/in-game-lists/types";
import type navItems from "~/components/layout/nav-items.json";
import { type AuthErrorCode } from "~/modules/auth";
export const SPLATOON_2_SENDOU_IN_URL = "https://spl2.sendou.ink";
export const PLUS_SERVER_DISCORD_URL = "https://discord.gg/FW4dKrY";
@ -59,6 +60,8 @@ export const userResultsEditHighlightsPage = (user: UserLinkArgs) =>
export const userNewBuildPage = (user: UserLinkArgs) =>
`${userBuildsPage(user)}/new`;
export const authErrorUrl = (errorCode: AuthErrorCode) =>
`/?authError=${errorCode}`;
export const impersonateUrl = (idToLogInAs: number) =>
`/auth/impersonate?id=${idToLogInAs}`;
export const badgePage = (badgeId: number) => `${BADGES_PAGE}/${badgeId}`;

View File

@ -27,6 +27,7 @@
"actions.remove": "Entfernen",
"actions.delete": "Löschen",
"actions.loadMore": "Mehr laden",
"actions.close": "Schließen",
"results": "Ergebnisse",
@ -54,5 +55,10 @@
"weapon.category.DUALIES": "Doppler",
"weapon.category.BRELLAS": "Pluviatoren",
"weapon.category.STRINGERS": "Stringer",
"weapon.category.SPLATANAS": "Splatanas"
"weapon.category.SPLATANAS": "Splatanas",
"auth.errors.aborted": "Login Abgebrochen",
"auth.errors.failed": "Login Fehlgeschlagen",
"auth.errors.discordPermissions": "Für dein sendou.ink Profil braucht die Seite die Erlaubnis deinen Discord-Namen, Avatar, und verbundene Social Accounts auszulesen.",
"auth.errors.unknown": "Der Login via Discord ist aus unbekannten Gründen fehlgeschlagen. Falls dies wiederholt auftritt, kontaktiere uns bitte."
}

View File

@ -14,6 +14,11 @@
"header.logout": "Log out",
"header.login": "Log in",
"auth.errors.aborted": "Login Aborted",
"auth.errors.failed": "Login Failed",
"auth.errors.discordPermissions": "For your sendou.ink profile, the site needs access to your Discord profile's name, avatar and social connections.",
"auth.errors.unknown": "The login via Discord failed for an unknown reason. If this keeps happening, please reach out for help.",
"footer.github.subtitle": "Source code",
"footer.twitter.subtitle": "Updates",
"footer.discord.subtitle": "Help & feedback",
@ -29,6 +34,7 @@
"actions.delete": "Delete",
"actions.loadMore": "Load more",
"actions.copyToClipboard": "Copy to clipboard",
"actions.close": "Close",
"maps.createMapList": "Create map list",
"maps.halfSz": "50% SZ",

View File

@ -1,6 +1,68 @@
# Translation Progress
## /da (🟢 Done)
## /da (🟡 In progress)
### 🟢 analyzer.json
**94/94**
### 🟢 badges.json
**7/7**
### 🟢 builds.json
**11/11**
### 🟢 calendar.json
**46/46**
### 🟡 common.json
**55/60**
<details>
<summary>Missing</summary>
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.close
</details>
### 🟢 contributions.json
**6/6**
### 🟢 faq.json
**6/6**
### 🟢 front.json
**11/11**
### 🟢 game-misc.json
**17/17**
### 🟡 user.json
**20/25**
<details>
<summary>Missing</summary>
- results.title
- results.highlights
- results.nonHighlights
- results.highlights.choose
- results.highlights.explanation
</details>
---
@ -32,7 +94,7 @@
### 🟡 common.json
**49/55**
**54/60**
<details>
<summary>Missing</summary>
@ -123,14 +185,19 @@
### 🟡 common.json
**48/55**
**48/60**
<details>
<summary>Missing</summary>
- pages.s2
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool
@ -239,7 +306,7 @@
### 🟡 common.json
**46/55**
**46/60**
<details>
<summary>Missing</summary>
@ -247,8 +314,13 @@
- pages.s2
- pages.analyzer
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.loadMore
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool
@ -350,7 +422,7 @@
### 🔴 common.json
**0/55**
**0/60**
### 🔴 contributions.json
@ -501,7 +573,7 @@
### 🟡 common.json
**46/55**
**46/60**
<details>
<summary>Missing</summary>
@ -509,8 +581,13 @@
- pages.s2
- pages.analyzer
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.loadMore
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool
@ -619,7 +696,7 @@
### 🟡 common.json
**35/55**
**35/60**
<details>
<summary>Missing</summary>
@ -627,8 +704,13 @@
- pages.s2
- pages.analyzer
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.loadMore
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool
@ -748,14 +830,19 @@
### 🟡 common.json
**48/55**
**48/60**
<details>
<summary>Missing</summary>
- pages.analyzer
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool
@ -843,7 +930,7 @@
### 🟡 common.json
**35/55**
**35/60**
<details>
<summary>Missing</summary>
@ -851,8 +938,13 @@
- pages.s2
- pages.analyzer
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.loadMore
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool
@ -972,7 +1064,7 @@
### 🟡 common.json
**35/55**
**35/60**
<details>
<summary>Missing</summary>
@ -980,8 +1072,13 @@
- pages.s2
- pages.analyzer
- pages.maps
- auth.errors.aborted
- auth.errors.failed
- auth.errors.discordPermissions
- auth.errors.unknown
- actions.loadMore
- actions.copyToClipboard
- actions.close
- maps.createMapList
- maps.halfSz
- maps.mapPool