Skip to content

Commit

Permalink
[Files] Verfy a sharing link asap and give nice error (#1674)
Browse files Browse the repository at this point in the history
* ugly redirect

* better UX

* lingui extract

* consistancy

* hide loader when link is invalid

* typo and icon color

* add button to go back

* lingui extract

* nice error with malformed jwt

* lingui extract

* redirect to /

Co-authored-by: GitHub Actions <actions@github.com>
  • Loading branch information
Tbaut and actions-user authored Nov 2, 2021
1 parent 4b6e437 commit 035326a
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 53 deletions.
4 changes: 2 additions & 2 deletions packages/files-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@babel/core": "^7.12.10",
"@babel/runtime": "^7.0.0",
"@chainsafe/browser-storage-hooks": "^1.0.1",
"@chainsafe/files-api-client": "1.18.15",
"@chainsafe/files-api-client": "^1.18.19",
"@chainsafe/web3-context": "1.1.4",
"@lingui/core": "^3.7.2",
"@lingui/react": "^3.7.2",
Expand All @@ -31,9 +31,9 @@
"ethers": "^5.4.3",
"fflate": "^0.7.1",
"formik": "^2.2.5",
"heic-convert": "^1.2.4",
"jsrsasign": "^10.4.1",
"key-encoder": "^2.0.3",
"heic-convert": "^1.2.4",
"mime-matcher": "^1.0.5",
"posthog-js": "^1.13.10",
"react": "^16.14.0",
Expand Down
72 changes: 49 additions & 23 deletions packages/files-ui/src/Components/Modules/LinkSharingModule.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"
import { Button, CheckCircleIcon, Loading, Typography, useHistory, useLocation } from "@chainsafe/common-components"
import { Button, CheckCircleIcon, ExclamationCircleIcon, Loading, Typography, useHistory, useLocation } from "@chainsafe/common-components"
import { getBucketDecryptionFromHash, getJWT } from "../../Utils/pathUtils"
import { useFilesApi } from "../../Contexts/FilesApiContext"
import { useThresholdKey } from "../../Contexts/ThresholdKeyContext"
import { Trans } from "@lingui/macro"
import { t, Trans } from "@lingui/macro"
import { useFiles } from "../../Contexts/FilesContext"
import jwtDecode from "jwt-decode"
import { createStyles, makeStyles } from "@chainsafe/common-theme"
Expand Down Expand Up @@ -37,12 +37,11 @@ const useStyles = makeStyles(
alignItems: "center",
fontSize: constants.generalUnit * 6,
"& svg": {
marginRight: constants.generalUnit,
fill: palette.additional["gray"][7]
}
},
error: {
color: palette.error.main
errorMessage: {
textAlign: "center"
},
messageWrapper: {
display: "flex",
Expand All @@ -56,10 +55,12 @@ const useStyles = makeStyles(
})
)

interface DecodedJwt {
export interface DecodedNonceJwt {
bucket_id?: string
permission?: NonceResponsePermission
nonce_id?: string
}

const LinkSharingModule = () => {
const { pathname, hash } = useLocation()
const { redirect } = useHistory()
Expand All @@ -71,15 +72,27 @@ const LinkSharingModule = () => {
const [encryptedEncryptionKey, setEncryptedEncryptionKey] = useState("")
const [error, setError] = useState("")
const classes = useStyles()
const { bucket_id: bucketId, permission } = useMemo(() => {
const { bucket_id: bucketId, permission, nonce_id } = useMemo(() => {
try {
return (jwt && jwtDecode<DecodedJwt>(jwt)) || {}
return (jwt && jwtDecode<DecodedNonceJwt>(jwt)) || {}
}catch (e) {
console.error(e)
setError(t`This link is marlformed. Please verify that you copy/pasted it correctly.`)
return {}
}
}, [jwt])
const newBucket = useMemo(() => buckets.find((b) => b.id === bucketId), [bucketId, buckets])
const [isValidNonce, setIsValidNonce] = useState<boolean | undefined>()

useEffect(() => {
if(!nonce_id) return

filesApiClient.isNonceValid(nonce_id)
.then((res) => {
setIsValidNonce(res.is_valid)
})
.catch(console.error)
}, [filesApiClient, nonce_id])

useEffect(() => {
if(!publicKey || !bucketDecryptionKey) return
Expand All @@ -91,7 +104,7 @@ const LinkSharingModule = () => {
}, [bucketDecryptionKey, encryptForPublicKey, publicKey])

useEffect(() => {
if(!jwt || !encryptedEncryptionKey || !!newBucket) return
if(!jwt || !encryptedEncryptionKey || !!newBucket || !isValidNonce) return

filesApiClient.verifyNonce({ jwt, encryption_key: encryptedEncryptionKey })
.catch((e:any) => {
Expand All @@ -101,7 +114,7 @@ const LinkSharingModule = () => {
.finally(() => {
refreshBuckets()
})
}, [encryptedEncryptionKey, error, filesApiClient, jwt, newBucket, refreshBuckets])
}, [encryptedEncryptionKey, error, filesApiClient, isValidNonce, jwt, newBucket, refreshBuckets])

const onBrowseBucket = useCallback(() => {
newBucket && redirect(ROUTE_LINKS.SharedFolderExplorer(newBucket.id, "/"))
Expand All @@ -111,25 +124,29 @@ const LinkSharingModule = () => {
<div className={classes.root}>
<div className={classes.box}>
<div className={classes.messageWrapper}>
{!error && !newBucket && (
{!error && !newBucket && isValidNonce !== false && (
<>
<Loading
type="inherit"
size={48}
className={classes.icon}
/>
<Typography variant={"h4"} >
<Trans>Adding you to the shared folder...</Trans>
<Typography variant="h4">
{isValidNonce === undefined
? <Trans>Verifying the link...</Trans>
: <Trans>Adding you to the shared folder...</Trans>
}

</Typography>
</>
)}
{!error && newBucket && permission && (
{!error && newBucket && permission && isValidNonce && (
<>
<CheckCircleIcon
size={48}
className={classes.icon}
/>
<Typography variant={"h4"} >
<Typography variant="h4">
<Trans>
You were added to the shared folder ({translatedPermission(permission)}): {newBucket.name}
</Trans>
Expand All @@ -142,15 +159,24 @@ const LinkSharingModule = () => {
</Button>
</>
)}
{(!!error || isValidNonce === false) && (
<>
<ExclamationCircleIcon
size={48}
className={classes.icon}
/>
<Typography
variant="h4"
className={classes.errorMessage}
>
{ isValidNonce === false
? <Trans>This link is not valid any more.</Trans>
: error
}
</Typography>
</>
)}
</div>
{!!error && (
<Typography
variant="body2"
className={classes.error}
>
{error}
</Typography>
)}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from "react"
import React, { useCallback, useEffect, useMemo, useState } from "react"
import {
Button,
GithubLogoIcon,
Expand All @@ -8,7 +8,9 @@ import {
Typography,
FormikTextInput,
EthereumLogoIcon,
useLocation
useLocation,
ExclamationCircleIcon,
useHistory
} from "@chainsafe/common-components"
import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme"
import { CSFTheme } from "../../../Themes/types"
Expand All @@ -23,6 +25,9 @@ import { IdentityProvider } from "@chainsafe/files-api-client"
import PasswordlessEmail from "./PasswordlessEmail"
import { Form, FormikProvider, useFormik } from "formik"
import { emailValidation } from "../../../Utils/validationSchema"
import { getJWT } from "../../../Utils/pathUtils"
import jwtDecode from "jwt-decode"
import { DecodedNonceJwt } from "../LinkSharingModule"
import dayjs from "dayjs"

const useStyles = makeStyles(
Expand Down Expand Up @@ -148,6 +153,13 @@ const useStyles = makeStyles(
secondaryLoginText: {
paddingTop: constants.generalUnit * 2
},
exclamationIcon: {
fontSize: 48,
"& svg": {
marginRight: constants.generalUnit,
fill: palette.additional["gray"][7]
}
},
maintenanceMessage: {
display: "block",
textAlign: "justify",
Expand Down Expand Up @@ -184,6 +196,30 @@ const InitialScreen = ({ className }: IInitialScreen) => {
const [email, setEmail] = useState("")
const { state } = useLocation<{from?: string}>()
const isSharing = useMemo(() => state?.from?.includes(LINK_SHARING_BASE), [state])
const [isValidNonce, setIsValidNonce] = useState<boolean | undefined>()
const { redirect } = useHistory()

useEffect(() => {
if (!isSharing) return

const jwt = getJWT(state?.from)
let nonce = ""

try {
nonce = (jwt && jwtDecode<DecodedNonceJwt>(jwt).nonce_id) || ""
}catch (e) {
setError(t`The link you typed in looks malformed. Please verify it.`)
console.error(e)
}

if (!nonce) return

filesApiClient.isNonceValid(nonce)
.then((res) => {
setIsValidNonce(res.is_valid)
})
.catch(console.error)
}, [filesApiClient, isSharing, state])

const handleSelectWalletAndConnect = async () => {
setError(undefined)
Expand All @@ -208,6 +244,7 @@ const InitialScreen = ({ className }: IInitialScreen) => {
setErrorEmail("")
setLoginMode(undefined)
resetStatus()
setIsValidNonce(undefined)
}

const handleLogin = async (loginType: IdentityProvider) => {
Expand Down Expand Up @@ -380,19 +417,21 @@ const InitialScreen = ({ className }: IInitialScreen) => {

return (
<div className={clsx(classes.root, className)}>
{loginMode !== "email" && ((desktop && !isConnecting && !error) || (isConnecting && loginMode !== "web3")) && (
{isValidNonce !== false &&
loginMode !== "email" &&
((desktop && !isConnecting && !error) || (isConnecting && loginMode !== "web3")) && (
<Typography
variant="h6"
component="h1"
className={classes.headerText}
>
{isSharing
{isSharing && status !== "logging in"
? <Trans>Sign in/up to access the shared folder</Trans>
: <Trans>Get Started</Trans>
}
</Typography>
)}
{!error && (
{!error && isValidNonce !== false && (
loginMode !== "web3" && loginMode !== "email"
? <>
<section className={classes.buttonSection}>
Expand Down Expand Up @@ -517,22 +556,40 @@ const InitialScreen = ({ className }: IInitialScreen) => {
: <WalletSelection />
)}
{!!error && (
<>
<section className={classes.connectingWallet}>
<Typography variant='h2'>
<Trans>Connection failed</Trans>
</Typography>
<Typography variant='h5'>
{error}
</Typography>
<Button
variant="primary"
onClick={resetLogin}
>
<Trans>Try again</Trans>
</Button>
</section>
</>
<section className={classes.connectingWallet}>
<Typography variant='h2'>
<Trans>Connection failed</Trans>
</Typography>
<Typography variant='h5'>
{error}
</Typography>
<Button
variant="primary"
onClick={resetLogin}
>
<Trans>Try again</Trans>
</Button>
</section>
)}
{isValidNonce === false && status !== "logging in" && (
<section className={classes.connectingWallet}>
<ExclamationCircleIcon
className={classes.exclamationIcon}
size={48}
/>
<Typography variant='h2'>
<Trans>This link is not valid any more.</Trans>
</Typography>
<Button
variant="primary"
onClick={() => {
resetLogin()
redirect("/")
}}
>
<Trans>Go to login</Trans>
</Button>
</section>
)}
</div>
)
Expand Down
5 changes: 4 additions & 1 deletion packages/files-ui/src/Utils/pathUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ export const isSubFolder = (fold1: string, fold2: string) => {
}

// get the jwt from /link-sharing/permision/jwt
export const getJWT = (pathname: string) => {
export const getJWT = (pathname?: string) => {

if(!pathname) return

const arrayOfPaths = getArrayOfPaths(pathname)

if(arrayOfPaths.length !== 3){
Expand Down
15 changes: 15 additions & 0 deletions packages/files-ui/src/locales/de/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ msgstr ""
msgid "Go back"
msgstr "Zurück"

msgid "Go to login"
msgstr ""

msgid "Got it"
msgstr ""

Expand Down Expand Up @@ -721,6 +724,9 @@ msgstr ""
msgid "The files are already in this folder"
msgstr ""

msgid "The link you typed in looks malformed. Please verify it."
msgstr ""

msgid "The username is too long"
msgstr ""

Expand Down Expand Up @@ -754,6 +760,12 @@ msgstr ""
msgid "There was an error when setting username."
msgstr ""

msgid "This link is marlformed. Please verify that you copy/pasted it correctly."
msgstr ""

msgid "This link is not valid any more."
msgstr ""

msgid "This username is already taken"
msgstr ""

Expand Down Expand Up @@ -817,6 +829,9 @@ msgstr "Verifizierungscode nicht korrekt!"
msgid "Verification code sent!"
msgstr "Verifizierungscode gesendet!"

msgid "Verifying the link..."
msgstr ""

msgid "View folder"
msgstr "Ordner anzeigen"

Expand Down
Loading

0 comments on commit 035326a

Please sign in to comment.