-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add git lens * add page * get read only endpoints of user objects from js package * fix paths and routes * about us page section * add target audience section * quotes section * coding clubs section * home page * merge from dev * new js package * delete images * debug current test file * quick save * add crypto-js * add --dev @types/crypto-js * new password form * fix errors * fix errors * merge from dev * new js package * feedback
- Loading branch information
Showing
23 changed files
with
450 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
VITE_API_BASE_URL=http://localhost:8000/api/ | ||
VITE_SERVICE_NAME=portal | ||
|
||
# Links external sites. | ||
VITE_LINK_OPEN_VERIFY_EMAIL_IN_GMAIL="https://mail.google.com/mail/#search/from%3Ano-reply%40info.codeforlife.education+subject%3AEmail+Verification" | ||
VITE_LINK_OPEN_VERIFY_EMAIL_IN_OUTLOOK=https://outlook.live.com/mail/ | ||
# Gmail. | ||
VITE_GMAIL_FILTERS_PASSWORD_RESET_REQUEST="from:[email protected] subject:Password reset request" | ||
VITE_GMAIL_FILTERS_EMAIL_VERIFICATION="from:[email protected] subject:Email verification" | ||
|
||
# Links to external sites. | ||
VITE_LINK_FEMALE_GRADUATES_IN_CS=https://www.wisecampaign.org.uk/core-stem-graduates-2019/ | ||
|
||
# TODO: determine which of these we need. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,80 @@ | ||
import * as yup from "yup" | ||
|
||
export const classIdSchema = yup | ||
.string() | ||
.matches(/^[A-Z0-9]{5}$/, "Invalid class code") | ||
|
||
const passwordSchema = yup.string().required("required") | ||
|
||
export const teacherPasswordSchema = passwordSchema.test({ | ||
message: "too-weak", | ||
test: password => | ||
password.length >= 10 && | ||
!( | ||
password.search(/[A-Z]/) === -1 || | ||
password.search(/[a-z]/) === -1 || | ||
password.search(/[0-9]/) === -1 || | ||
password.search(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/) === -1 | ||
), | ||
}) | ||
|
||
export const studentPasswordSchema = passwordSchema.test({ | ||
message: "too-weak", | ||
test: password => password.length >= 6, | ||
}) | ||
|
||
export const indyPasswordSchema = passwordSchema.test({ | ||
message: "too-weak", | ||
test: password => | ||
password.length >= 8 && | ||
!( | ||
password.search(/[A-Z]/) === -1 || | ||
password.search(/[a-z]/) === -1 || | ||
password.search(/[0-9]/) === -1 | ||
), | ||
}) | ||
import CryptoJS from "crypto-js" | ||
import { string as YupString, type Schema, type StringSchema } from "yup" | ||
|
||
type Options<S extends Schema, Extras = {}> = Partial<{ schema: S } & Extras> | ||
|
||
export function classIdSchema(options?: Options<StringSchema>) { | ||
const { schema = YupString() } = options || {} | ||
|
||
return schema.matches(/^[A-Z0-9]{5}$/, "invalid class code") | ||
} | ||
|
||
export function teacherPasswordSchema(options?: Options<StringSchema>) { | ||
const { schema = YupString() } = options || {} | ||
|
||
return schema | ||
.min(10, "password must be at least 10 characters long") | ||
.matches(/[A-Z]/, "password must contain at least one uppercase letter") | ||
.matches(/[a-z]/, "password must contain at least one lowercase letter") | ||
.matches(/[0-9]/, "password must contain at least one digit") | ||
.matches( | ||
/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/, | ||
"password must contain at least one special character", | ||
) | ||
} | ||
|
||
export function studentPasswordSchema(options?: Options<StringSchema>) { | ||
const { schema = YupString() } = options || {} | ||
|
||
return schema.min(6, "password must be at least 6 characters long") | ||
} | ||
|
||
export function indyPasswordSchema(options?: Options<StringSchema>) { | ||
const { schema = YupString() } = options || {} | ||
|
||
return schema | ||
.min(8, "password must be at least 8 characters long") | ||
.matches(/[A-Z]/, "password must contain at least one uppercase letter") | ||
.matches(/[a-z]/, "password must contain at least one lowercase letter") | ||
.matches(/[0-9]/, "password must contain at least one digit") | ||
} | ||
|
||
export function pwnedPasswordSchema( | ||
options?: Options<StringSchema, { onError: (error: unknown) => void }>, | ||
) { | ||
const { schema = YupString().required(), onError } = options || {} | ||
|
||
return schema.test({ | ||
message: "password is too common", | ||
test: async password => { | ||
try { | ||
// Do not raise validation error if no password. | ||
if (!password) return true | ||
|
||
// Hash the password. | ||
const hashedPassword = CryptoJS.SHA1(password).toString().toUpperCase() | ||
const hashPrefix = hashedPassword.substring(0, 5) | ||
const hashSuffix = hashedPassword.substring(5) | ||
|
||
// Call Pwned Passwords API. | ||
// https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange | ||
const response = await fetch( | ||
`https://api.pwnedpasswords.com/range/${hashPrefix}`, | ||
) | ||
// TODO: Standardize how to log non-okay responses. | ||
if (!response.ok) throw Error() | ||
|
||
// Parse response. | ||
const data = await response.text() | ||
return !data.includes(hashSuffix) | ||
} catch (error) { | ||
console.error(error) | ||
|
||
if (onError) onError(error) | ||
|
||
// Do not raise validation error if a different error occurred. | ||
return true | ||
} | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { Button, Dialog, Typography } from "@mui/material" | ||
import { type FC, useState } from "react" | ||
|
||
import { PasswordField } from "codeforlife/components/form" | ||
|
||
import { | ||
indyPasswordSchema, | ||
pwnedPasswordSchema, | ||
studentPasswordSchema, | ||
teacherPasswordSchema, | ||
} from "../app/schemas" | ||
|
||
export interface NewPasswordFieldProps { | ||
userType: "teacher" | "independent" | "student" | ||
} | ||
|
||
const NewPasswordField: FC<NewPasswordFieldProps> = ({ userType }) => { | ||
const [pwnedPasswords, setPwnedPasswords] = useState<{ | ||
online: boolean | ||
dialogOpen: boolean | ||
}>({ online: true, dialogOpen: false }) | ||
|
||
let schema = { | ||
teacher: teacherPasswordSchema, | ||
independent: indyPasswordSchema, | ||
student: studentPasswordSchema, | ||
}[userType]() | ||
|
||
if ( | ||
pwnedPasswords.online && | ||
(userType === "teacher" || userType === "independent") | ||
) { | ||
schema = pwnedPasswordSchema({ | ||
schema, | ||
onError: () => { | ||
// Alert user test couldn't be carried out. | ||
setPwnedPasswords({ online: false, dialogOpen: true }) | ||
}, | ||
}) | ||
} | ||
|
||
return ( | ||
<> | ||
<PasswordField | ||
required | ||
withRepeatField | ||
schema={schema} | ||
validateOptions={{ abortEarly: false }} | ||
/> | ||
<Dialog open={!pwnedPasswords.online && pwnedPasswords.dialogOpen}> | ||
<Typography variant="h5" className="no-override"> | ||
Password Vulnerability Check Unavailable | ||
</Typography> | ||
<Typography className="no-override"> | ||
We are currently unable to check your password vulnerability. Please | ||
ensure that you are using a strong password. If you are happy to | ||
continue, please confirm. | ||
</Typography> | ||
<Button | ||
className="no-override" | ||
onClick={() => { | ||
setPwnedPasswords({ online: false, dialogOpen: false }) | ||
}} | ||
> | ||
I understand | ||
</Button> | ||
</Dialog> | ||
</> | ||
) | ||
} | ||
|
||
export default NewPasswordField |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { MailOutline as MailOutlineIcon } from "@mui/icons-material" | ||
import { Stack } from "@mui/material" | ||
import { type FC } from "react" | ||
|
||
import { LinkButton } from "codeforlife/components/router" | ||
|
||
export interface OpenInEmailButtonsProps { | ||
gmailFilters: string | ||
} | ||
|
||
const OpenInEmailButtons: FC<OpenInEmailButtonsProps> = ({ gmailFilters }) => { | ||
return ( | ||
<Stack direction="row" spacing={5}> | ||
<LinkButton | ||
to={`https://mail.google.com/mail/#search/${encodeURIComponent(gmailFilters)}`} | ||
target="_blank" | ||
endIcon={<MailOutlineIcon />} | ||
> | ||
Open in Gmail | ||
</LinkButton> | ||
<LinkButton | ||
to="https://outlook.live.com/mail/" | ||
target="_blank" | ||
endIcon={<MailOutlineIcon />} | ||
> | ||
Open in Outlook | ||
</LinkButton> | ||
</Stack> | ||
) | ||
} | ||
|
||
export default OpenInEmailButtons |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import NewPasswordField, { | ||
type NewPasswordFieldProps, | ||
} from "./NewPasswordField" | ||
import OpenInEmailButtons, { | ||
type OpenInEmailButtonsProps, | ||
} from "./OpenInEmailButtons" | ||
|
||
export { | ||
NewPasswordField, | ||
OpenInEmailButtons, | ||
type NewPasswordFieldProps, | ||
type OpenInEmailButtonsProps, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.