diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index 03a9a7717..14c717e38 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -1,32 +1,20 @@ import { Request, Response } from "express"; -import fs from "fs"; -import path from "path"; import jwt from "jsonwebtoken"; import * as bigintConversion from "bigint-conversion"; // eslint-disable-next-line @typescript-eslint/no-var-requires const jsrp = require("jsrp"); -import { - LoginSRPDetail, - TokenVersion, - User, -} from "../../models"; +import { LoginSRPDetail, TokenVersion, User } from "../../models"; import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth"; import { checkUserDevice } from "../../helpers/user"; -import { - ACTION_LOGIN, - ACTION_LOGOUT, -} from "../../variables"; -import { - BadRequestError, - UnauthorizedRequestError, -} from "../../utils/errors"; +import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables"; +import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { EELogService } from "../../ee/services"; import { getUserAgentType } from "../../utils/posthog"; import { getHttpsEnabled, getJwtAuthLifetime, getJwtAuthSecret, - getJwtRefreshSecret, + getJwtRefreshSecret } from "../../config"; import { ActorType } from "../../ee/models"; @@ -44,13 +32,10 @@ declare module "jsonwebtoken" { * @returns */ export const login1 = async (req: Request, res: Response) => { - const { - email, - clientPublicKey, - }: { email: string; clientPublicKey: string } = req.body; + const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body; const user = await User.findOne({ - email, + email }).select("+salt +verifier"); if (!user) throw new Error("Failed to find user"); @@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => { server.init( { salt: user.salt, - verifier: user.verifier, + verifier: user.verifier }, async () => { // generate server-side public key const serverPublicKey = server.getPublicKey(); - await LoginSRPDetail.findOneAndReplace({ email: email }, { - email: email, - clientPublicKey: clientPublicKey, - serverBInt: bigintConversion.bigintToBuf(server.bInt), - }, { upsert: true, returnNewDocument: false }) + await LoginSRPDetail.findOneAndReplace( + { email: email }, + { + email: email, + clientPublicKey: clientPublicKey, + serverBInt: bigintConversion.bigintToBuf(server.bInt) + }, + { upsert: true, returnNewDocument: false } + ); return res.status(200).send({ serverPublicKey, - salt: user.salt, + salt: user.salt }); } ); @@ -89,15 +78,19 @@ export const login1 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => { const { email, clientProof } = req.body; const user = await User.findOne({ - email, + email }).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag"); if (!user) throw new Error("Failed to find user"); - const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email }) + const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email }); if (!loginSRPDetailFromDB) { - return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) + return BadRequestError( + Error( + "It looks like some details from the first login are not found. Please try login one again" + ) + ); } const server = new jsrp.server(); @@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => { { salt: user.salt, verifier: user.verifier, - b: loginSRPDetailFromDB.serverBInt, + b: loginSRPDetailFromDB.serverBInt }, async () => { server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); @@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => { await checkUserDevice({ user, ip: req.realIP, - userAgent: req.headers["user-agent"] ?? "", + userAgent: req.headers["user-agent"] ?? "" }); - const tokens = await issueAuthTokens({ + const tokens = await issueAuthTokens({ userId: user._id, ip: req.realIP, - userAgent: req.headers["user-agent"] ?? "", + userAgent: req.headers["user-agent"] ?? "" }); // store (refresh) token in httpOnly cookie @@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: "/", sameSite: "strict", - secure: await getHttpsEnabled(), + secure: await getHttpsEnabled() }); const loginAction = await EELogService.createAction({ name: ACTION_LOGIN, - userId: user._id, + userId: user._id }); - loginAction && await EELogService.createLog({ - userId: user._id, - actions: [loginAction], - channel: getUserAgentType(req.headers["user-agent"]), - ipAddress: req.realIP, - }); + loginAction && + (await EELogService.createLog({ + userId: user._id, + actions: [loginAction], + channel: getUserAgentType(req.headers["user-agent"]), + ipAddress: req.realIP + })); // return (access) token in response return res.status(200).send({ @@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => { publicKey: user.publicKey, encryptedPrivateKey: user.encryptedPrivateKey, iv: user.iv, - tag: user.tag, + tag: user.tag }); } return res.status(400).send({ - message: "Failed to authenticate. Try again?", + message: "Failed to authenticate. Try again?" }); } ); @@ -171,7 +165,7 @@ export const login2 = async (req: Request, res: Response) => { */ export const logout = async (req: Request, res: Response) => { if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) { - await clearTokens(req.authData.tokenVersionId) + await clearTokens(req.authData.tokenVersionId); } // clear httpOnly cookie @@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => { httpOnly: true, path: "/", sameSite: "strict", - secure: (await getHttpsEnabled()) as boolean, + secure: (await getHttpsEnabled()) as boolean }); const logoutAction = await EELogService.createAction({ name: ACTION_LOGOUT, - userId: req.user._id, + userId: req.user._id }); - logoutAction && await EELogService.createLog({ - userId: req.user._id, - actions: [logoutAction], - channel: getUserAgentType(req.headers["user-agent"]), - ipAddress: req.realIP, - }); + logoutAction && + (await EELogService.createLog({ + userId: req.user._id, + actions: [logoutAction], + channel: getUserAgentType(req.headers["user-agent"]), + ipAddress: req.realIP + })); return res.status(200).send({ - message: "Successfully logged out.", + message: "Successfully logged out." }); }; -export const getCommonPasswords = async (req: Request, res: Response) => { - const commonPasswords = fs.readFileSync( - path.resolve(__dirname, "../../data/" + "common_passwords.txt"), - "utf8" - ).split("\n"); - - return res.status(200).send(commonPasswords); -} - export const revokeAllSessions = async (req: Request, res: Response) => { - await TokenVersion.updateMany({ - user: req.user._id, - }, { - $inc: { - refreshVersion: 1, - accessVersion: 1, + await TokenVersion.updateMany( + { + user: req.user._id }, - }); + { + $inc: { + refreshVersion: 1, + accessVersion: 1 + } + } + ); return res.status(200).send({ - message: "Successfully revoked all sessions.", - }); -} + message: "Successfully revoked all sessions." + }); +}; /** * Return user is authenticated @@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => { */ export const checkAuth = async (req: Request, res: Response) => { return res.status(200).send({ - message: "Authenticated", + message: "Authenticated" }); -} +}; /** * Return new JWT access token by first validating the refresh token @@ -244,47 +233,47 @@ export const checkAuth = async (req: Request, res: Response) => { export const getNewToken = async (req: Request, res: Response) => { const refreshToken = req.cookies.jid; - if (!refreshToken) throw BadRequestError({ - message: "Failed to find refresh token in request cookies" - }); + if (!refreshToken) + throw BadRequestError({ + message: "Failed to find refresh token in request cookies" + }); - const decodedToken = ( - jwt.verify(refreshToken, await getJwtRefreshSecret()) - ); + const decodedToken = jwt.verify(refreshToken, await getJwtRefreshSecret()); const user = await User.findOne({ - _id: decodedToken.userId, + _id: decodedToken.userId }).select("+publicKey +refreshVersion +accessVersion"); if (!user) throw new Error("Failed to authenticate unfound user"); - if (!user?.publicKey) - throw new Error("Failed to authenticate not fully set up account"); - + if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account"); + const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId); - if (!tokenVersion) throw UnauthorizedRequestError({ - message: "Failed to validate refresh token", - }); + if (!tokenVersion) + throw UnauthorizedRequestError({ + message: "Failed to validate refresh token" + }); - if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({ - message: "Failed to validate refresh token", - }); + if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) + throw BadRequestError({ + message: "Failed to validate refresh token" + }); const token = createToken({ payload: { userId: decodedToken.userId, tokenVersionId: tokenVersion._id.toString(), - accessVersion: tokenVersion.refreshVersion, + accessVersion: tokenVersion.refreshVersion }, expiresIn: await getJwtAuthLifetime(), - secret: await getJwtAuthSecret(), + secret: await getJwtAuthSecret() }); return res.status(200).send({ - token, + token }); }; export const handleAuthProviderCallback = (req: Request, res: Response) => { res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`); -} +}; diff --git a/backend/src/data/common_passwords.txt b/backend/src/data/common_passwords.txt deleted file mode 100644 index 01a442b17..000000000 --- a/backend/src/data/common_passwords.txt +++ /dev/null @@ -1,1497 +0,0 @@ -123456 -123456789 -111111 -password -qwerty -abc123 -12345678 -password1 -1234567 -123123 -1234567890 -000000 -12345 -iloveyou -1q2w3e4r5t -1234 -123456a -qwertyuiop -monkey -123321 -dragon -654321 -666666 -123 -myspace1 -a123456 -121212 -1qaz2wsx -123qwe -123abc -tinkle -target123 -gwerty -1g2w3e4r -gwerty123 -zag12wsx -7777777 -qwerty1 -1q2w3e4r -987654321 -222222 -qwe123 -qwerty123 -zxcvbnm -555555 -112233 -fuckyou -asdfghjkl -12345a -123123123 -1q2w3e -qazwsx -computer -aaaaaa -159753 -iloveyou1 -fuckyou1 -princess -789456123 -11111111 -123654 -princess1 -888888 -linkedin -michael -sunshine -football -11111 -777777 -1234qwer -999999 -j38ifUbn -monkey1 -football1 -daniel -azerty -a12345 -123456789a -789456 -asdfgh -love123 -abcd1234 -jordan23 -88888888 -5201314 -12qwaszx -FQRG7CS493 -ashley -asdf -asd123 -superman -jessica -love -samsung -shadow -blink182 -333333 -michael1 -babygirl1 -jesus1 -qwert -k.: -baseball -charlie -0 -hello1 -soccer -killer -131313 -master -1111111 -gfhjkm -0123456789 -987654 -iloveyou2 -angel1 -jordan -147258369 -bitch1 -michelle -q1w2e3r4 -jessica1 -qwer1234 -159357 -soccer1 -liverpool -101010 -zxcvbn -thomas -asdasd -fuckyou2 -justin -nicole -1111111111 -1 -1111 -qazwsxedc -baseball1 -andrew -hello -apple -0987654321 -anthony1 -102030 -money1 -parola -abc -147258 -anthony -111222 -jennifer -number1 -naruto -123456q -696969 -00000000 -joshua -golfer -29rsavoy -myspace -andrea -basketball -qwerty12 -charlie1 -passw0rd -asshole1 -hunter -marina -welcome -010203 -superman1 -password12 -xbox360 -sunshine1 -ashley1 -lovely -babygirl -! -trustno1 -666 -asdf1234 -chocolate -buster -summer -tigger -purple -freedom -loveme -matthew -50cent -password2 -maggie -george -chelsea -12341234 -amanda -hannah -q1w2e3 -friends -shadow1 -william -abcdefg -samantha -12344321 -nicole1 -q1w2e3r4t5y6 -robert -mother -jordan1 -secret -letmein -qweasdzxc -212121 -pokemon -$HEX -internet -batman -love12 -a123456789 -VQsaBLPzLa -qweqwe -hello123 -232323 -butterfly -martin -flower -forever -mustang -1qazxsw2 -iloveu -cjmasterinf -orange -harley -user -brandon1 -london -1234567891 -pepper -chris1 -lol123 -abcdef -whatever -1342 -alexander -loveyou -290966 -wall.e -junior -12413 -qweasd -PE#5GZ29PTZMSE -tudelft -dpbk1234 -DIOSESFIEL -U38fa39 -147852 -cookie -family -jasmine -dragon1 -12345q -nikita -pakistan -123654789 -123789 -amanda1 -joseph -happy1 -ginger -: -matthew1 -snoopy -justin1 -lastfm -3rJs1la7qE -пїЅпїЅпїЅпїЅпїЅпїЅ -antonio -barcelona -matrix -computer1 -hottie1 -sophie -sandra -michelle1 -12345678910 -qqqqqq -arsenal -444444 -brandon -daniel1 -jonathan -killer1 -liverpool1 -mickey -ghbdtn -purple1 -mercedes -patrick -11223344 -diamond -456789 -victoria -asshole -taylor -qwertyu -andrew1 -red123 -lucky1 -eminem -12345qwert -111222tianya -yellow -william1 -bailey -angel -chicken1 -richard -0000 -banana -0000000000 -jasmine1 -benjamin -welcome1 -starwars -hunter1 -cheese -melissa -angela -christian -1234554321 -oliver -chocolate1 -butterfly1 -peanut -55555 -hockey -mylove -natasha -NULL -mommy1 -1234561 -q1w2e3r4t5 -america -252525 -monster -school -456123 -james1 -slipknot -hannah1 -zaq12wsx -chicken -147852369 -gabriel -elizabeth -cookie1 -Status -87654321 -robert1 -ferrari -nathan -1password -buddy1 -1314520 -america1 -metallica -chelsea1 -zzzzzz -prince -adidas -jackson -morgan -rainbow -silver -1234567a -angels -iw14Fi9j -loveme1 -juventus -jennifer1 -!~!1 -bubbles -samuel -fuckoff -lovers -cheese1 -0123456 -123asd -999999999 -madison -elizabeth1 -music -buster1 -lauren -david1 -tigger1 -123qweasd -taylor1 -carlos -tinkerbell -samantha1 -Sojdlg123aljg -joshua1 -poop -stella -myspace123 -asdasd5 -freedom1 -whatever1 -xxxxxx -00000 -valentina -a1b2c3 -741852963 -austin -monica -qaz123 -lovely1 -music1 -harley1 -family1 -spongebob1 -steven -nirvana -1234abcd -hellokitty -thomas1 -7654321 -madison1 -daddy1 -summer1 -cocacola -nicholas -zxc123 -123456m -qwertyui -spiderman -vanessa -diamond1 -142536 -danielle -badoo -7758521 -bandit -pokemon1 -mustang1 -1qaz2wsx3edc -alexis -loulou -justinbieb -yamaha -qwert1 -scooter -rachel -tennis -ronaldo -i -mexico1 -friends1 -victor -maggie1 -asdfasdf -qwerty12345 -lover1 -jesus -123hfjdk147 -nicolas -batman1 -weed420 -password123 -loser1 -123456j -iloveyou! -pepper1 -fuckoff1 -555666 -iloveu2 -sabrina -pussy1 -bubbles1 -098765 -master1 -smokey -a1b2c3d4 -123456789q -qwaszx -heather -jasper -booboo -heather1 -4815162342 -peanut1 -chester -123456s -123456b -google -edward -yankees1 -canada -Exigent -destiny -success -nigger1 -135790 -asdfghjkl1 -124578 -casper -lalala -mother1 -sexy123 -qazxsw -naruto1 -1q2w3e4r5t6y -david -money -yellow1 -patrick1 -flower1 -12121212 -alexander1 -raiders1 -Password1 -sebastian -134679 -zxcvbnm1 -dennis -852456 -hahaha -daniela -ginger1 -olivia -melissa1 -010101 -slipknot1 -spiderman1 -cowboys1 -0000000 -rebecca -741852 -jeremy -a1234567 -dakota -123456d -1a2b3c -apple1 -november -alexandra -159951 -iloveu1 -veronica -fuckme1 -baby123 -yankees -stupid1 -cristina -newyork1 -jackson1 -playboy -friend -iloveyou12 -sammy1 -pimpin1 -phoenix -PolniyPizdec0211 -rocky1 -password! -joseph1 -753951 -p -a838hfiD -richard1 -beautiful1 -mickey1 -carolina -j123456 -202020 -newyork -patricia -charles -stephanie -orange1 -m123456 -421uiopy258 -myspace2 -cameron -spider -barbie -woaini -vincent -mexico -scorpion -monster1 -aaaaa -elephant -asdf123 -963852741 -zk.: -guitar -fucker1 -destiny1 -hotmail -johnny -doudou -q123456 -bailey1 -asdfgh1 -fucker -louise -sparky -sweety -123456abc -shorty1 -booboo1 -december -9876543210 -manchester -midnight -246810 -jessie -dallas -austin1 -s123456 -pass -12345678a -claudia -пїЅпїЅпїЅпїЅпїЅпїЅпїЅ -kristina -lakers -lovelove -crazy1 -tiger1 -thunder -dolphin -a -gangsta1 -jackie -151515 -charlotte -scooter1 -caroline -fuck -merlin -junior1 -super123 -scooby -marseille -aaaa -metallica1 -kitty1 -chris -beautiful -black1 -danielle1 -blessed1 -skater1 -1029384756 -qazwsx123 -456456 -b123456 -genius -guitar1 -tyler1 -peaches -california -sakura -tigers -soleil -lauren1 -green1 -smokey1 -cooper -520520 -muffin -christian1 -love13 -fucku2 -arsenal1 -lucky7 -diablo -apples -george1 -babyboy1 -crystal -1122334455 -player1 -aa123456 -vfhbyf -forever1 -Password -winston -chivas1 -sexy -hockey1 -1a2b3c4d -pussy -playboy1 -stalker -cherry -tweety -toyota -creative -gemini -pretty1 -пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ -maverick -brittany1 -nathan1 -letmein1 -cameron1 -secret1 -google1 -heaven -martina -murphy -spongebob -uQA9Ebw445 -fernando -pretty -startfinding -softball -dolphin1 -fuckme -test123 -qwerty1234 -kobe24 -alejandro -adrian -september -aaaaaa1 -bubba1 -isabella -abc123456 -password3 -jason1 -abcdefg123 -loveyou1 -shannon -100200 -manuel -leonardo -molly1 -flowers -123456z -007007 -password. -321321 -miguel -samsung1 -sergey -sweet1 -abc1234 -windows -qwert123 -vfrcbv -poohbear -d123456 -school1 -badboy -951753 -123456c -111 -steven1 -snoopy1 -garfield -YAgjecc826 -compaq -candy1 -sarah1 -qwerty123456 -123456l -eminem1 -141414 -789789 -maria -steelers -iloveme1 -morgan1 -winner -boomer -lolita -nastya -alexis1 -carmen -angelo -nicholas1 -portugal -precious -jackass1 -jonathan1 -yfnfif -bitch -tiffany -rabbit -rainbow1 -angel123 -popcorn -barbara -brandy -fuckyou! -starwars1 -barney -natalia -hiphop -tiffany1 -shorty -poohbear1 -simone -albert -marlboro -hardcore -cowboys -sydney -alex -scorpio -1234512345 -q12345 -qq123456 -onelove -bond007 -abcdefg1 -eagles -crystal1 -azertyuiop -winter -sexy12 -angelina -james -svetlana -fatima -123456k -icecream -popcorn1 -121314 -john316 -qazwsx1 -victoria1 -twilight -iloveme -9379992 -pass123 -dancer -brittany -beauty -bonjour -maxwell -coffee -dexter -454545 -qazqaz -snickers -love11 -samson -aaaaaaaa -swordfish -fyfcnfcbz -abcd123 -aaa111 -natalie -hottie -passion -alyssa -rockstar1 -lovers1 -florida -alicia -happy -blue123 -123456t -ranger -yourmom1 -pumpkin -denise -edward1 -tweety1 -christine -august -54321 -bella1 -marie1 -seven7 -steelers1 -aaaaa1 -shannon1 -amber1 -cutie1 -peaches1 -florida1 -bonnie -stephanie1 -lollipop -cassie -k. -rachel1 -greenday1 -krishna -teresa -october -iverson3 -motorola -rockstar -hahaha1 -police -lakers24 -fylhtq -andrey -loveme2 -turtle -southside1 -baby -bismillah -pa55word -blessed -emmanuel -666999 -012345 -fluffy -5555555555 -stupid -karina -fishing -musica -password11 -love4ever -melanie -greenday -isabelle -nothing -abcd -chicago -cowboy -mnbvcxz -andrea1 -242424 -babygurl1 -santiago -ssssss -kevin1 -lakers1 -chester1 -321654 -kimberly -carlos1 -z123456 -daisy1 -jackass -m -5555555 -zoosk -boston -happy123 -55555555 -satan666 -111111a -pamela -090909 -francesco -horses -456852 -qwer -vanessa1 -redsox -pookie -a12345678 -110110 -tucker -marley -corvette -778899 -realmadrid -raiders -rangers -people -1123581321 -soccer12 -sayang -shelby -christ -12345t -fktrcfylh -kitten -player -c123456 -qwert12345 -baby12 -trinity -1v7Upjw3nT -p@ssw0rd -thunder1 -zxcvbnm123 -midnight1 -lebron23 -golden -strawberry -orlando -love1234 -lucky13 -asdfg1 -marine -soccer10123456 -password -12345678 -1234 -pussy -12345 -dragon -qwerty -696969 -mustang -letmein -baseball -master -michael -football -shadow -monkey -abc123 -pass -fuckme -6969 -jordan -harley -ranger -iwantu -jennifer -hunter -fuck -2000 -test -batman -trustno1 -thomas -tigger -robert -access -love -buster -1234567 -soccer -hockey -killer -george -sexy -andrew -charlie -superman -asshole -fuckyou -dallas -jessica -panties -pepper -1111 -austin -william -daniel -golfer -summer -heather -hammer -yankees -joshua -maggie -biteme -enter -ashley -thunder -cowboy -silver -richard -fucker -orange -merlin -michelle -corvette -bigdog -cheese -matthew -121212 -patrick -martin -freedom -ginger -blowjob -nicole -sparky -yellow -camaro -secret -dick -falcon -taylor -111111 -131313 -123123 -bitch -hello -scooter -please -porsche -guitar -chelsea -black -diamond -nascar -jackson -cameron -654321 -computer -amanda -wizard -xxxxxxxx -money -phoenix -mickey -bailey -knight -iceman -tigers -purple -andrea -horny -dakota -aaaaaa -player -sunshine -morgan -starwars -boomer -cowboys -edward -charles -girls -booboo -coffee -xxxxxx -bulldog -ncc1701 -rabbit -peanut -john -johnny -gandalf -spanky -winter -brandy -compaq -carlos -tennis -james -mike -brandon -fender -anthony -blowme -ferrari -cookie -chicken -maverick -chicago -joseph -diablo -sexsex -hardcore -666666 -willie -welcome -chris -panther -yamaha -justin -banana -driver -marine -angels -fishing -david -maddog -hooters -wilson -butthead -dennis -fucking -captain -bigdick -chester -smokey -xavier -steven -viking -snoopy -blue -eagles -winner -samantha -house -miller -flower -jack -firebird -butter -united -turtle -steelers -tiffany -zxcvbn -tomcat -golf -bond007 -bear -tiger -doctor -gateway -gators -angel -junior -thx1138 -porno -badboy -debbie -spider -melissa -booger -1212 -flyers -fish -porn -matrix -teens -scooby -jason -walter -cumshot -boston -braves -yankee -lover -barney -victor -tucker -princess -mercedes -5150 -doggie -zzzzzz -gunner -horney -bubba -2112 -fred -johnson -xxxxx -tits -member -boobs -donald -bigdaddy -bronco -penis -voyager -rangers -birdie -trouble -white -topgun -bigtits -bitches -green -super -qazwsx -magic -lakers -rachel -slayer -scott -2222 -asdf -video -london -7777 -marlboro -srinivas -internet -action -carter -jasper -monster -teresa -jeremy -11111111 -bill -crystal -peter -pussies -cock -beer -rocket -theman -oliver -prince -beach -amateur -7777777 -muffin -redsox -star -testing -shannon -murphy -frank -hannah -dave -eagle1 -11111 -mother -nathan -raiders -steve -forever -angela -viper -ou812 -jake -lovers -suckit -gregory -buddy -whatever -young -nicholas -lucky -helpme -jackie -monica -midnight -college -baby -cunt -brian -mark -startrek -sierra -leather -232323 -4444 -beavis -bigcock -happy -sophie -ladies -naughty -giants -booty -blonde -fucked -golden -0 -fire -sandra -pookie -packers -einstein -dolphins -chevy -winston -warrior -sammy -slut -8675309 -zxcvbnm -nipples -power -victoria -asdfgh -vagina -toyota -travis -hotdog -paris -rock -xxxx -extreme -redskins -erotic -dirty -ford -freddy -arsenal -access14 -wolf -nipple -iloveyou -alex -florida -eric -legend -movie -success -rosebud -jaguar -great -cool -cooper -1313 -scorpio -mountain -madison -987654 -brazil -lauren -japan -naked -squirt -stars -apple -alexis -aaaa -bonnie -peaches -jasmine -kevin -matt -qwertyui -danielle -beaver -4321 -4128 -runner -swimming -dolphin -gordon -casper -stupid -shit -saturn -gemini -apples -august -3333 -canada -blazer -cumming -hunting -kitty -rainbow -112233 -arthur -cream -calvin -shaved -surfer -samson -kelly -paul -mine -king -racing -5555 -eagle -hentai -newyork -little -redwings -smith -sticky -cocacola -animal -broncos -private -skippy -marvin -blondes -enjoy -girl -apollo -parker -qwert -time -sydney -women -voodoo -magnum -juice -abgrtyu -777777 -dreams -maxwell -music -rush2112 -russia -scorpion -rebecca -tester -mistress -phantom -billy -6666 -albert \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index 17c030fed..098da2aad 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -24,7 +24,7 @@ import { secretSnapshot as eeSecretSnapshotRouter, users as eeUsersRouter, workspace as eeWorkspaceRouter, - secretScanning as v1SecretScanningRouter, + secretScanning as v1SecretScanningRouter } from "./ee/routes/v1"; import { auth as v1AuthRouter, @@ -58,7 +58,7 @@ import { signup as v2SignupRouter, tags as v2TagsRouter, users as v2UsersRouter, - workspace as v2WorkspaceRouter, + workspace as v2WorkspaceRouter } from "./routes/v2"; import { auth as v3AuthRouter, @@ -70,14 +70,21 @@ import { healthCheck } from "./routes/status"; import { getLogger } from "./utils/logger"; import { RouteNotFoundError } from "./utils/errors"; import { requestErrorHandler } from "./middleware/requestErrorHandler"; -import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config"; +import { + getNodeEnv, + getPort, + getSecretScanningGitAppId, + getSecretScanningPrivateKey, + getSecretScanningWebhookProxy, + getSecretScanningWebhookSecret, + getSiteURL +} from "./config"; import { setup } from "./utils/setup"; import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices"; import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; -const SmeeClient = require('smee-client') // eslint-disable-line +const SmeeClient = require("smee-client"); // eslint-disable-line const main = async () => { - await setup(); await EELicenseService.initGlobalFeatureSet(); @@ -94,11 +101,15 @@ const main = async () => { }) ); - if (await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey()) { + if ( + (await getSecretScanningGitAppId()) && + (await getSecretScanningWebhookSecret()) && + (await getSecretScanningPrivateKey()) + ) { const probot = new Probot({ appId: await getSecretScanningGitAppId(), privateKey: await getSecretScanningPrivateKey(), - secret: await getSecretScanningWebhookSecret(), + secret: await getSecretScanningWebhookSecret() }); if ((await getNodeEnv()) != "production") { @@ -106,12 +117,14 @@ const main = async () => { source: await getSecretScanningWebhookProxy(), target: "http://backend:4000/ss-webhook", logger: console - }) + }); - smee.start() + smee.start(); } - app.use(createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })); // secret scanning webhook + app.use( + createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" }) + ); // secret scanning webhook } if ((await getNodeEnv()) === "production") { @@ -207,8 +220,8 @@ const main = async () => { server.on("close", async () => { await DatabaseService.closeDatabase(); - syncSecretsToThirdPartyServices.close() - githubPushEventSecretScan.close() + syncSecretsToThirdPartyServices.close(); + githubPushEventSecretScan.close(); }); return server; diff --git a/backend/src/routes/v1/auth.ts b/backend/src/routes/v1/auth.ts index f373567f2..d9c0d6120 100644 --- a/backend/src/routes/v1/auth.ts +++ b/backend/src/routes/v1/auth.ts @@ -8,7 +8,8 @@ import { AuthMode } from "../../variables"; router.post("/token", validateRequest, authController.getNewToken); -router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1) +router.post( + // TODO endpoint: deprecate (moved to api/v3/auth/login1) "/login1", authLimiter, body("email").exists().trim().notEmpty().toLowerCase(), @@ -17,7 +18,8 @@ router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1) authController.login1 ); -router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login2) +router.post( + // TODO endpoint: deprecate (moved to api/v3/auth/login2) "/login2", authLimiter, body("email").exists().trim().notEmpty().toLowerCase(), @@ -30,7 +32,7 @@ router.post( "/logout", authLimiter, requireAuth({ - acceptedAuthModes: [AuthMode.JWT], + acceptedAuthModes: [AuthMode.JWT] }), authController.logout ); @@ -38,24 +40,19 @@ router.post( router.post( "/checkAuth", requireAuth({ - acceptedAuthModes: [AuthMode.JWT], + acceptedAuthModes: [AuthMode.JWT] }), authController.checkAuth ); -router.get( - "/common-passwords", - authLimiter, - authController.getCommonPasswords -); - -router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions) +router.delete( + // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions) "/sessions", authLimiter, requireAuth({ - acceptedAuthModes: [AuthMode.JWT], - }), + acceptedAuthModes: [AuthMode.JWT] + }), authController.revokeAllSessions ); -export default router; \ No newline at end of file +export default router; diff --git a/frontend/next.config.js b/frontend/next.config.js index b133818bd..3e9336f15 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -3,7 +3,7 @@ /** * @type {import('next').NextConfig} **/ -const path = require('path'); +const path = require("path"); const ContentSecurityPolicy = ` default-src 'self'; @@ -11,7 +11,7 @@ const ContentSecurityPolicy = ` style-src 'self' https://rsms.me 'unsafe-inline'; child-src https://api.stripe.com; frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/; - connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com http://localhost:*; + connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://localhost:*; img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:; media-src https://js.intercomcdn.com; font-src 'self' https://fonts.intercomcdn.com/ https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com; @@ -21,50 +21,50 @@ const ContentSecurityPolicy = ` // after learning more below. const securityHeaders = [ { - key: 'X-DNS-Prefetch-Control', - value: 'on' + key: "X-DNS-Prefetch-Control", + value: "on" }, { - key: 'Strict-Transport-Security', - value: 'max-age=63072000; includeSubDomains; preload' + key: "Strict-Transport-Security", + value: "max-age=63072000; includeSubDomains; preload" }, { - key: 'X-XSS-Protection', - value: '1; mode=block' + key: "X-XSS-Protection", + value: "1; mode=block" }, { - key: 'X-Frame-Options', - value: 'SAMEORIGIN' + key: "X-Frame-Options", + value: "SAMEORIGIN" }, { - key: 'Permissions-Policy', - value: 'camera=(), microphone=()' + key: "Permissions-Policy", + value: "camera=(), microphone=()" }, { - key: 'X-Content-Type-Options', - value: 'nosniff' + key: "X-Content-Type-Options", + value: "nosniff" }, { - key: 'Referrer-Policy', - value: 'strict-origin-when-cross-origin' + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin" }, { - key: 'Content-Security-Policy', - value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim() + key: "Content-Security-Policy", + value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim() } ]; module.exports = { - output: 'standalone', + output: "standalone", i18n: { - locales: ['en', 'ko', 'fr', 'pt-BR', 'pt-PT', 'es'], - defaultLocale: 'en' + locales: ["en", "ko", "fr", "pt-BR", "pt-PT", "es"], + defaultLocale: "en" }, async headers() { return [ { // Apply these headers to all routes in your application. - source: '/:path*', + source: "/:path*", headers: securityHeaders } ]; @@ -73,15 +73,15 @@ module.exports = { // config config.module.rules.push({ test: /\.wasm$/, - loader: 'base64-loader', - type: 'javascript/auto' + loader: "base64-loader", + type: "javascript/auto" }); config.module.noParse = /\.wasm$/; config.module.rules.forEach((rule) => { (rule.oneOf || []).forEach((oneOf) => { - if (oneOf.loader && oneOf.loader.indexOf('file-loader') >= 0) { + if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) { oneOf.exclude.push(/\.wasm$/); } }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 568410bb2..990b03377 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -102,7 +102,7 @@ "@storybook/testing-library": "^0.2.0", "@tailwindcss/typography": "^0.5.4", "@types/jsrp": "^0.2.4", - "@types/node": "18.11.9", + "@types/node": "^18.11.9", "@types/react": "^18.0.26", "@types/sanitize-html": "^2.9.0", "@typescript-eslint/eslint-plugin": "^5.48.1", diff --git a/frontend/package.json b/frontend/package.json index 9aa355d4b..fab8b7033 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -110,7 +110,7 @@ "@storybook/testing-library": "^0.2.0", "@tailwindcss/typography": "^0.5.4", "@types/jsrp": "^0.2.4", - "@types/node": "18.11.9", + "@types/node": "^18.11.9", "@types/react": "^18.0.26", "@types/sanitize-html": "^2.9.0", "@typescript-eslint/eslint-plugin": "^5.48.1", diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json index a70a7dd8b..cea11c2ea 100644 --- a/frontend/public/locales/en/translations.json +++ b/frontend/public/locales/en/translations.json @@ -231,10 +231,15 @@ "current": "Current password", "current-wrong": "The current password may be wrong", "new": "New password", - "validate-base": "Password should contain at least:", - "validate-length": "14 characters", - "validate-case": "1 lowercase character", - "validate-number": "1 number" + "validate-base": "Password should contain:", + "validate-tooShort": "at least 14 characters", + "validate-tooLong": "at most 100 characters", + "validate-noLetterChar": "at least 1 letter character", + "validate-noNumOrSpecialChar": "at least 1 number or special character", + "validate-repeatedChar": "at most 3 repeated, consecutive characters", + "validate-escapeChar": "No escape characters allowed.", + "validate-lowEntropy": "Password contains sensitive data.", + "validate-breached": "Password was found in a data breach." }, "token": { "service-tokens": "Service Tokens", diff --git a/frontend/public/locales/es/translations.json b/frontend/public/locales/es/translations.json index e734fb3f2..44da9a8ce 100644 --- a/frontend/public/locales/es/translations.json +++ b/frontend/public/locales/es/translations.json @@ -228,10 +228,15 @@ "current": "Contraseña actual", "current-wrong": "La contraseña actual puede puede que sea incorrecta", "new": "Nueva contraseña", - "validate-base": "La contraseña debe contener como mínimo:", - "validate-length": "14 caracteres", - "validate-case": "1 letra en minúsculas", - "validate-number": "1 número" + "validate-base": "La contraseña debe contener:", + "validate-tooShort": "al menos 14 caracteres", + "validate-tooLong": "como máximo 100 caracteres", + "validate-noLetterChar": "al menos 1 carácter alfabético", + "validate-noNumOrSpecialChar": "al menos 1 número o carácter especial", + "validate-repeatedChar": "como máximo 3 caracteres repetidos y consecutivos", + "validate-escapeChar": "No se permiten caracteres de escape.", + "validate-lowEntropy": "La contraseña contiene datos sensibles.", + "validate-breached": "La contraseña se encontró en una violación de datos." }, "token": { "service-tokens": "Tokens de servicio", diff --git a/frontend/public/locales/fr/translations.json b/frontend/public/locales/fr/translations.json index 6914e7ea1..60d5cf8cf 100644 --- a/frontend/public/locales/fr/translations.json +++ b/frontend/public/locales/fr/translations.json @@ -215,10 +215,15 @@ "current": "Mot de passe actuel", "current-wrong": "Le mot de passe actuel peut être érroné", "new": "Nouveau mot de passe", - "validate-base": "Le mot de passe doit contenir au moins:", - "validate-length": "14 caractères", - "validate-case": "1 caractère miniscule", - "validate-number": "1 chiffre" + "validate-base": "Le mot de passe doit contenir :", + "validate-tooShort": "au moins 14 caractères", + "validate-tooLong": "au plus 100 caractères", + "validate-noLetterChar": "au moins 1 caractère alphabétique", + "validate-noNumOrSpecialChar": "au moins 1 chiffre ou caractère spécial", + "validate-repeatedChar": "au plus 3 caractères consécutifs répétés", + "validate-escapeChar": "Aucun caractère d'échappement autorisé.", + "validate-lowEntropy": "Le mot de passe contient des données sensibles.", + "validate-breached": "Le mot de passe a été trouvé dans une violation de données." }, "token": { "service-tokens": "Jetons de service", @@ -296,4 +301,4 @@ "step5-subtitle": "Infisical a pour but d'être utilisé avec vos coéquipiers. Invitez-les à le tester.", "step5-skip": "Passer" } -} \ No newline at end of file +} diff --git a/frontend/public/locales/ko/translations.json b/frontend/public/locales/ko/translations.json index eea81f37e..e8169eaba 100644 --- a/frontend/public/locales/ko/translations.json +++ b/frontend/public/locales/ko/translations.json @@ -182,10 +182,15 @@ "current": "현재 비밀번호", "new": "새 비밀번호", "current-wrong": "현재 비밀번호가 잘못되었어요", - "validate-base": "비밀번호는 다음 조건을 만족해야 합니다:", - "validate-length": "14 글자 이상", - "validate-case": "1개 이상의 소문자", - "validate-number": "1개 이상의 숫자" + "validate-base": "비밀번호는 다음을 포함해야 합니다:", + "validate-tooShort": "최소 14자", + "validate-tooLong": "최대 100자", + "validate-noLetterChar": "최소 1개의 문자를 포함해야 합니다.", + "validate-noNumOrSpecialChar": "최소 1개의 숫자 또는 특수 문자를 포함해야 합니다.", + "validate-repeatedChar": "연속으로 최대 3개의 반복된 문자를 포함할 수 있습니다.", + "validate-escapeChar": "이스케이프 문자는 허용되지 않습니다.", + "validate-lowEntropy": "비밀번호에 민감한 데이터가 포함되어 있습니다.", + "validate-breached": "비밀번호가 데이터 유출에 포함되었습니다." }, "token": { "add-dialog": { @@ -256,4 +261,4 @@ "step4-description3": "분실시 접근하거나 복구할 수 없는 시크릿 키가 포함되어 있어요.", "step4-download": "PDF 다운로드" } -} \ No newline at end of file +} diff --git a/frontend/public/locales/pt-BR/translations.json b/frontend/public/locales/pt-BR/translations.json index 5b53ce503..ba324849b 100644 --- a/frontend/public/locales/pt-BR/translations.json +++ b/frontend/public/locales/pt-BR/translations.json @@ -210,10 +210,15 @@ "current": "Senha atual", "current-wrong": "A senha atual pode estar errada", "new": "Nova Senha", - "validate-base": "A senha deve conter pelo menos:", - "validate-length": "14 caracteres", - "validate-case": "1 caractere minúsculo", - "validate-number": "1 número" + "validate-base": "A senha deve conter:", + "validate-tooShort": "pelo menos 14 caracteres", + "validate-tooLong": "no máximo 100 caracteres", + "validate-noLetterChar": "pelo menos 1 caractere alfabético", + "validate-noNumOrSpecialChar": "pelo menos 1 número ou caractere especial", + "validate-repeatedChar": "no máximo 3 caracteres repetidos e consecutivos", + "validate-escapeChar": "Nenhum caractere de escape permitido.", + "validate-lowEntropy": "A senha contém dados sensíveis.", + "validate-breached": "A senha foi encontrada em uma violação de dados." }, "token": { "service-tokens": "Tokens de Serviço", @@ -290,4 +295,4 @@ "step5-subtitle": "Infisical foi feito para ser usado com seus colegas. Convide-os para testar também.", "step5-skip": "Pular" } -} \ No newline at end of file +} diff --git a/frontend/public/locales/tr/translations.json b/frontend/public/locales/tr/translations.json index 706998495..93f228f96 100644 --- a/frontend/public/locales/tr/translations.json +++ b/frontend/public/locales/tr/translations.json @@ -228,10 +228,15 @@ "current": "Mevcut şifre", "current-wrong": "Mevcut şifre yanlış olabilir", "new": "Yeni şifre", - "validate-base": "Şifre en az şunları içermelidir:", - "validate-length": "14 karakter", - "validate-case": "1 küçük harf", - "validate-number": "1 rakam" + "validate-base": "Parola içermelidir:", + "validate-tooShort": "en az 14 karakter", + "validate-tooLong": "en fazla 100 karakter", + "validate-noLetterChar": "en az 1 harf karakteri", + "validate-noNumOrSpecialChar": "en az 1 rakam veya özel karakter", + "validate-repeatedChar": "en fazla 3 tekrarlanan, ardışık karakter", + "validate-escapeChar": "Kaçış karakterlerine izin verilmez.", + "validate-lowEntropy": "Parola hassas veriler içeriyor.", + "validate-breached": "Parola veri ihlalinde bulundu." }, "token": { "service-tokens": "Servis Belirteçleri", diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index db4d040ad..c09e43fcb 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -8,13 +8,12 @@ import jsrp from "jsrp"; import nacl from "tweetnacl"; import { encodeBase64 } from "tweetnacl-util"; -import { useGetCommonPasswords } from "@app/hooks/api"; import { completeAccountSignup } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import ProjectService from "@app/services/ProjectService"; import InputField from "../basic/InputField"; -import checkPassword from "../utilities/checks/checkPassword"; +import checkPassword from "../utilities/checks/password/checkPassword"; import Aes256Gcm from "../utilities/cryptography/aes-256-gcm"; import { deriveArgonKey } from "../utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "../utilities/saveTokenToLocalStorage"; @@ -39,12 +38,14 @@ interface UserInfoStepProps { } type Errors = { - length?: string, - upperCase?: string, - lowerCase?: string, - number?: string, - specialChar?: string, - repeatedChar?: string, + tooShort?: string; + tooLong?: string; + noLetterChar?: string; + noNumOrSpecialChar?: string; + repeatedChar?: string; + escapeChar?: string; + lowEntropy?: string; + breached?: string; }; /** @@ -71,9 +72,8 @@ export default function UserInfoStep({ setOrganizationName, attributionSource, setAttributionSource, - providerAuthToken, + providerAuthToken }: UserInfoStepProps): JSX.Element { - const { data: commonPasswords } = useGetCommonPasswords(); const [nameError, setNameError] = useState(false); const [organizationNameError, setOrganizationNameError] = useState(false); @@ -99,10 +99,9 @@ export default function UserInfoStep({ } else { setOrganizationNameError(false); } - - errorCheck = checkPassword({ + + errorCheck = await checkPassword({ password, - commonPasswords, setErrors }); @@ -174,7 +173,7 @@ export default function UserInfoStep({ salt: result.salt, verifier: result.verifier, organizationName, - attributionSource, + attributionSource }); // unset signup JWT token and set JWT token @@ -191,7 +190,7 @@ export default function UserInfoStep({ }); const userOrgs = await fetchOrganizations(); - + const orgId = userOrgs[0]?._id; const project = await ProjectService.initProject({ organizationId: orgId, @@ -215,13 +214,15 @@ export default function UserInfoStep({ }; return ( -
-

+

+

{t("signup.step3-message")}

-
-
-

Your Name

+
+
+

+ Your Name +

setName(e.target.value)} @@ -230,10 +231,16 @@ export default function UserInfoStep({ autoComplete="given-name" className="h-12" /> - {nameError &&

Please, specify your name

} + {nameError && ( +

+ Please, specify your name +

+ )}
-
-

Organization Name

+
+

+ Organization Name +

setOrganizationName(e.target.value)} @@ -241,10 +248,16 @@ export default function UserInfoStep({ isRequired className="h-12" /> - {organizationNameError &&

Please, specify your organization name

} + {organizationNameError && ( +

+ Please, specify your organization name +

+ )}
-
-

Where did you hear about us? (optional)

+
+

+ Where did you hear about us? (optional) +

setAttributionSource(e.target.value)} @@ -252,16 +265,15 @@ export default function UserInfoStep({ className="h-12" />
-
+
{ - setPassword(pass); - checkPassword({ + onChangeHandler={async (pass: string) => { + await checkPassword({ password: pass, - commonPasswords, setErrors }); + setPassword(pass); }} type="password" value={password} @@ -272,23 +284,20 @@ export default function UserInfoStep({ /> {Object.keys(errors).length > 0 && (
-
{t("section.password.validate-base")}
+
+ {t("section.password.validate-base")} +
{Object.keys(errors).map((key) => { if (errors[key as keyof Errors]) { return ( -
+
-
-

- {errors[key as keyof Errors]} -

+

{errors[key as keyof Errors]}

); } @@ -298,18 +307,21 @@ export default function UserInfoStep({
)}
-
-
+
+
+ > + {" "} + {String(t("signup.signup"))}{" "} +
diff --git a/frontend/src/components/utilities/checks/PasswordCheck.ts b/frontend/src/components/utilities/checks/PasswordCheck.ts deleted file mode 100644 index 5fb9dfe2c..000000000 --- a/frontend/src/components/utilities/checks/PasswordCheck.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable no-param-reassign */ -interface PasswordCheckProps { - password: string; - errorCheck: boolean; - setPasswordErrorLength: (value: boolean) => void; - setPasswordErrorNumber: (value: boolean) => void; - setPasswordErrorLowerCase: (value: boolean) => void; -} - -/** - * This function checks a user password with respect to some criteria. - */ -const passwordCheck = ({ - password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - errorCheck -}: PasswordCheckProps) => { - - if (!password || password.length < 14) { - setPasswordErrorLength(true); - errorCheck = true; - } else { - setPasswordErrorLength(false); - } - - if (!/\d/.test(password)) { - setPasswordErrorNumber(true); - errorCheck = true; - } else { - setPasswordErrorNumber(false); - } - - if (!/[a-z]/.test(password)) { - setPasswordErrorLowerCase(true); - errorCheck = true; - // } else if (/(.)(?:(?!\1).){1,2}/.test(password)) { - // console.log(111) - // setPasswordError(true); - // setPasswordErrorMessage("Password should not contain repeating characters."); - // errorCheck = true; - // } else if (RegExp(`[${email}]`).test(password)) { - // console.log(222) - // setPasswordError(true); - // setPasswordErrorMessage("Password should not contain your email."); - // errorCheck = true; - } else { - setPasswordErrorLowerCase(false); - } - - // if (!/[A-Z]/.test(password)) { - // setPasswordErrorUpperCase(true); - // errorCheck = true; - // } else { - // setPasswordErrorUpperCase(false); - // } - - // if (!/(?=.*[!@#$%^&*])/.test(password)) { - // setPasswordErrorSpecialChar(true); - // // "Please add at least 1 special character (*, !, #, %)." - // errorCheck = true; - // } else { - // setPasswordErrorSpecialChar(false); - // } - return errorCheck; -}; - -export default passwordCheck; diff --git a/frontend/src/components/utilities/checks/checkPassword.ts b/frontend/src/components/utilities/checks/checkPassword.ts deleted file mode 100644 index 69dba2397..000000000 --- a/frontend/src/components/utilities/checks/checkPassword.ts +++ /dev/null @@ -1,72 +0,0 @@ -type Errors = { - length?: string, - upperCase?: string, - lowerCase?: string, - number?: string, - specialChar?: string, - repeatedChar?: string, - commonPassword?: string - }; - -interface CheckPasswordParams { - password: string; - commonPasswords: string[]; - setErrors: (value: Errors) => void; -} - -/** - * Validate that the password [password] is at least: - * - 8 characters long - * - Contains 1 uppercase character (A-Z) - * - Contains 1 lowercase character (a-z) - * - Contains 1 number (0-9) - * - Does not contain 3 repeat, consecutive characters - * - * The function returns whether or not the password [password] - * passes the minimum requirements above. It sets errors on - * an erorr object via [setErrors]. - * - * @param {Object} obj - * @param {String} obj.password - the password to check - * @param {Function} obj.setErrors - set state function to set error object - */ -const checkPassword = ({ - password, - commonPasswords, - setErrors -}: CheckPasswordParams): boolean => { - const errors: Errors = {}; - - if (password.length < 8) { - errors.length = "8 characters"; - } - - if (!/[A-Z]/.test(password)) { - errors.upperCase = "1 uppercase character (A-Z)"; - } - - if (!/[a-z]/.test(password)) { - errors.lowerCase = "1 lowercase character (a-z)"; - } - - if (!/[0-9]/.test(password)) { - errors.number = "1 number (0-9)"; - } - - if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { - errors.specialChar = "1 special character (!@#$%^&*(),.?)"; - } - - if (/([A-Za-z0-9])\1\1\1/.test(password)) { - errors.repeatedChar = "No 3 repeat, consecutive characters"; - } - - if (commonPasswords.includes(password)) { - errors.commonPassword = "No common passwords"; - } - - setErrors(errors); - return Object.keys(errors).length > 0; -} - -export default checkPassword; \ No newline at end of file diff --git a/frontend/src/components/utilities/checks/password/PasswordCheck.ts b/frontend/src/components/utilities/checks/password/PasswordCheck.ts new file mode 100644 index 000000000..90ea4c7ea --- /dev/null +++ b/frontend/src/components/utilities/checks/password/PasswordCheck.ts @@ -0,0 +1,89 @@ +import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; +import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes"; + +interface PasswordCheckProps { + password: string; + setPasswordErrorTooShort: (value: boolean) => void; + setPasswordErrorTooLong: (value: boolean) => void; + setPasswordErrorNoLetterChar: (value: boolean) => void; + setPasswordErrorNoNumOrSpecialChar: (value: boolean) => void; + setPasswordErrorRepeatedChar: (value: boolean) => void; + setPasswordErrorEscapeChar: (value: boolean) => void; + setPasswordErrorLowEntropy: (value: boolean) => void; + setPasswordErrorBreached: (value: boolean) => void; +} + +const passwordCheck = async ({ + password, + setPasswordErrorTooShort, + setPasswordErrorTooLong, + setPasswordErrorNoLetterChar, + setPasswordErrorNoNumOrSpecialChar, + setPasswordErrorRepeatedChar, + setPasswordErrorEscapeChar, + setPasswordErrorLowEntropy, + setPasswordErrorBreached +}: PasswordCheckProps) => { + let errorCheck = false; + const tests = [ + { + name: "tooShort", + validator: (pwd: string) => pwd.length >= 14, + setError: setPasswordErrorTooShort, + }, + { + name: "tooLong", + validator: (pwd: string) => pwd.length < 101, + setError: setPasswordErrorTooLong, + }, + { + name: "noLetterChar", + validator: (pwd: string) => letterCharRegex.test(pwd), + setError: setPasswordErrorNoLetterChar, + }, + { + name: "noNumOrSpecialChar", + validator: (pwd: string) => numAndSpecialCharRegex.test(pwd), + setError: setPasswordErrorNoNumOrSpecialChar, + }, + { + name: "repeatedChar", + validator: (pwd: string) => !repeatedCharRegex.test(pwd), + setError: setPasswordErrorRepeatedChar, + }, + { + name: "escapeChar", + validator: (pwd: string) => !escapeCharRegex.test(pwd), + setError: setPasswordErrorEscapeChar, + }, + { + name: "lowEntropy", + validator: (pwd: string) => ( + !lowEntropyRegexes.some(regex => regex.test(pwd)) + ), + setError: setPasswordErrorLowEntropy, + }, + ]; + + const isBreached = await checkIsPasswordBreached(password); + + if (isBreached) { + errorCheck = true; + setPasswordErrorBreached(true); + } else { + setPasswordErrorBreached(false); + } + + tests.forEach((test) => { + if (!test.validator(password)) { + errorCheck = true; + test.setError(true); + } else { + test.setError(false); + } + }) + + return errorCheck; +}; + +export default passwordCheck; diff --git a/frontend/src/components/utilities/checks/password/checkIsPasswordBreached.ts b/frontend/src/components/utilities/checks/password/checkIsPasswordBreached.ts new file mode 100644 index 000000000..d978441a6 --- /dev/null +++ b/frontend/src/components/utilities/checks/password/checkIsPasswordBreached.ts @@ -0,0 +1,113 @@ +import axios from "axios"; + +// SHA-1 hash the password using the SubtleCrypto API +async function hashPassword(passwordBytes: ArrayBuffer): Promise { + const buffer = await window.crypto.subtle.digest("SHA-1", passwordBytes); + return buffer; +} + +// Convert the hashed password buffer to a hexadecimal string +function bufferToHex(buffer: ArrayBuffer): string { + const byteArray = new Uint8Array(buffer); + const hexParts: string[] = []; + byteArray.forEach((byte) => { + const hex = byte.toString(16).padStart(2, "0"); + hexParts.push(hex); + }); + return hexParts.join(""); +} + + // see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange + // in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table + // this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results + // returns a hash table of 800-1000 results + // padding has been added to prevent MitM attacker determining which hash table was called by the response size + // the last 35 chars of the password hash are compared client-side against the table + // if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted + // the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion + // https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/ + + // The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf + // "When processing requests to establish and change memorized secrets, verifiers SHALL compare + // the prospective secrets against a list that contains values known to be commonly-used, expected, + // or compromised. For example, the list MAY include, but is not limited to: + // • Passwords obtained from previous breach corpuses. + // • Dictionary words. + // • Repetitive or sequential characters (e.g. ‘aaaaaa’, ‘1234abcd’). + // • Context-specific words, such as the name of the service, the username, and derivatives + // thereof." + +export const checkIsPasswordBreached = async (password: string): Promise => { + const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com"; + const maxRetryAttempts = 3; + + let encodedPwd: Uint8Array | undefined; + let hashedPwdBuffer: ArrayBuffer | undefined; + + try { + // Convert the password to a Uint8Array (UTF-8 encoded bytes) + const textEncoder = new TextEncoder(); + encodedPwd = textEncoder.encode(password); + + // Hash the password and convert it to a useful format for the HIBP API + hashedPwdBuffer = await hashPassword(encodedPwd!.buffer); + const hashedPwd = bufferToHex(hashedPwdBuffer).toUpperCase(); + // ONLY send the first 5 hash chars (over HTTPS) + const hashedPwdToSend = hashedPwd.slice(0, 5); + const safeHashedPwdToSend = encodeURIComponent(hashedPwdToSend); // Ensure URL safety + const rangedHashTableUri = `${HAVE_I_BEEN_PWNED_API_URL}/range/${safeHashedPwdToSend}`; + + let response; + let retryAttempt = 0; + + /* eslint-disable no-await-in-loop */ + while (retryAttempt < maxRetryAttempts) { + try { + response = await axios.get(rangedHashTableUri, { + headers: { + "Add-Padding": "true", // see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/ + "Content-Type": "text/plain", + }, + }); + + if (response.status === 200) { + // now we get back one of 16^5 hash prefix tables with random padding + const responseData = response.data.toUpperCase(); + // check the last 35 hash chars to see if there's a match + const isBreachedPassword: boolean = responseData.includes(hashedPwd.slice(5, 40)); + return isBreachedPassword; + } + retryAttempt += 1; + + } catch (err) { + if (!axios.isAxiosError(err)) { + throw err; + } + retryAttempt += 1; + } + } + + console.error( + `Received a non-200 response (${response ? response.status : "unknown"}) from the Pwnd Passwords API` + ); + return false; + } catch (err: any) { + console.error("An unexpected error has occurred:", err.message); + return false; + } finally { + + // Clear the UTF-8 encoded password from memory + + if (encodedPwd) { + const zeroEncodedPwdBuffer = new Uint8Array(encodedPwd.length); + encodedPwd.set(zeroEncodedPwdBuffer); + } + + // Clear the hashed password buffer from memory + + if (hashedPwdBuffer) { + const zeroHashedPwdBuffer = new Uint8Array(hashedPwdBuffer); + zeroHashedPwdBuffer.fill(0); + } + } +}; diff --git a/frontend/src/components/utilities/checks/password/checkPassword.ts b/frontend/src/components/utilities/checks/password/checkPassword.ts new file mode 100644 index 000000000..55fede5a6 --- /dev/null +++ b/frontend/src/components/utilities/checks/password/checkPassword.ts @@ -0,0 +1,99 @@ +import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; +import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes"; + +type Errors = { + tooShort?: string; + tooLong?: string; + noLetterChar?: string; + noNumOrSpecialChar?: string; + repeatedChar?: string; + escapeChar?: string; + lowEntropy?: string; + breached?: string; +}; + +interface CheckPasswordParams { + password: string; + setErrors: (value: Errors) => void; +} + +/** + * Validate that the password [password]: + * - Contains at least 14 characters + * - Contains at most 100 characters + * - Contains at least 1 letter character (many languages supported) (case insensitive) + * - Contains at least 1 number (0-9) or special character (emojis included) + * - Does not contain 3 repeat, consecutive characters + * - Does not contain any escape characters/sequences + * - Does not contain PII and/or low entropy data (eg. email address, URL, phone number, DoB, SSN, driver's license, passport) + * - Is not in a database of breached passwords + * + * The function returns whether or not the password [password] + * passes the minimum requirements above. It sets errors on + * an erorr object via [setErrors]. + * + * @param {Object} obj + * @param {String} obj.password - the password to check + * @param {Function} obj.setErrors - set state function to set error object + */ + +const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Promise => { + const errors: Errors = {}; + + const tests = [ + { + name: "tooShort", + validator: (pwd: string) => pwd.length >= 14, + errorText: "at least 14 characters", + }, + { + name: "tooLong", + validator: (pwd: string) => pwd.length < 101, + errorText: "at most 100 characters", + }, + { + name: "noLetterChar", + validator: (pwd: string) => letterCharRegex.test(pwd), + errorText: "at least 1 letter character", + }, + { + name: "noNumOrSpecialChar", + validator: (pwd: string) => numAndSpecialCharRegex.test(pwd), + errorText: "at least 1 number or special character", + }, + { + name: "repeatedChar", + validator: (pwd: string) => !repeatedCharRegex.test(pwd), + errorText: "at most 3 repeated, consecutive characters", + }, + { + name: "escapeChar", + validator: (pwd: string) => !escapeCharRegex.test(pwd), + errorText: "No escape characters allowed.", + }, + { + name: "lowEntropy", + validator: (pwd: string) => ( + !lowEntropyRegexes.some(regex => regex.test(pwd)) + ), + errorText: "Password contains sensitive data.", + }, + ]; + + const isBreached = await checkIsPasswordBreached(password); + + if (isBreached) { + errors.breached = "Password was found in a data breach."; + } + + tests.forEach((test) => { + if (test.validator && !test.validator(password)) { + errors[test.name as keyof Errors] = test.errorText; + } + }); + + setErrors(errors); + return Object.keys(errors).length > 0; +}; + +export default checkPassword; \ No newline at end of file diff --git a/frontend/src/components/utilities/checks/password/passwordRegexes.ts b/frontend/src/components/utilities/checks/password/passwordRegexes.ts new file mode 100644 index 000000000..c28d8da21 --- /dev/null +++ b/frontend/src/components/utilities/checks/password/passwordRegexes.ts @@ -0,0 +1,36 @@ +// This regex covers letters (case insensitive) for the top 50 most spoken languages +/* eslint-disable no-misleading-character-class */ +export const letterCharRegex = /[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u; + +// This regex covers digits, special characters, symbols, and emojis. +export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u; + +// This regex covers 3 repeated consecutive chars (incl. spaces) +export const repeatedCharRegex = /(.)\1\1\1|\s{4,}/; + +// This regex covers the escape sequences as a precaution +export const escapeCharRegex = /[\n\t\r\\]/; + +// This regex covers some PII and/or low entropy data +export const lowEntropyRegexes = [ + // Email address + /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, + + // URL (incl. subdomains, paths, top-level domains & query params) + /^(?:(?:https?|ftp):\/\/)?(?:\w+\.)?[a-zA-Z0-9.-]+\.(?:com|org|net|edu)(?:\/\S*)?(?:\?\S*)?$/, + + // Date in various formats + /(\b\d{1,4}[-/.]?\d{1,2}[-/.]?\d{1,4}\b)|(\b\d{1,4}[-/.]?\w{3}[-/.]?\d{1,4}\b)/, + + // Phone numbers (generalized) + /(?:\+(?:[1-9]\d{0,2})\s?)?(?:\(\d{1,4}\)\s?)?(?:\d[-.\s]?){5,}\d/, + + // Passport numbers (generalized) + /\b(?:[A-Z0-9]{6,9}|[A-Z0-9]{8,9}|[A-Z0-9]{9}|[A-Z0-9]{10,11})\b/, + + // Driver's license numbers (generalized) + /\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/, + + // US social security number + /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/, +]; \ No newline at end of file diff --git a/frontend/src/hooks/api/auth/index.tsx b/frontend/src/hooks/api/auth/index.tsx index dbcc77a5a..66208a487 100644 --- a/frontend/src/hooks/api/auth/index.tsx +++ b/frontend/src/hooks/api/auth/index.tsx @@ -1,10 +1,10 @@ export { useGetAuthToken, - useGetCommonPasswords, useResetPassword, - useSendMfaToken, + useSendMfaToken, useSendPasswordResetEmail, useSendVerificationEmail, useVerifyEmailVerificationCode, useVerifyMfaToken, - useVerifyPasswordResetCode} from "./queries" + useVerifyPasswordResetCode +} from "./queries"; diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index 094fd4b61..a26297730 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -20,22 +20,22 @@ import { SRPR1Res, VerifyMfaTokenDTO, VerifyMfaTokenRes, - VerifySignupInviteDTO} from "./types"; + VerifySignupInviteDTO +} from "./types"; const authKeys = { - getAuthToken: ["token"] as const, - commonPasswords: ["common-passwords"] as const + getAuthToken: ["token"] as const }; export const login1 = async (loginDetails: Login1DTO) => { const { data } = await apiRequest.post("/api/v3/auth/login1", loginDetails); return data; -} +}; export const login2 = async (loginDetails: Login2DTO) => { const { data } = await apiRequest.post("/api/v3/auth/login2", loginDetails); return data; -} +}; export const useLogin1 = () => { return useMutation({ @@ -47,7 +47,7 @@ export const useLogin1 = () => { return login1(details); } }); -} +}; export const useLogin2 = () => { return useMutation({ @@ -59,22 +59,22 @@ export const useLogin2 = () => { return login2(details); } }); -} +}; export const srp1 = async (details: SRP1DTO) => { const { data } = await apiRequest.post("/api/v1/password/srp1", details); - return data; -} + return data; +}; export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => { const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details); - return data; -} + return data; +}; export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => { const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details); - return data; -} + return data; +}; export const useCompleteAccountSignup = () => { return useMutation({ @@ -82,7 +82,7 @@ export const useCompleteAccountSignup = () => { return completeAccountSignup(details); } }); -} +}; export const useSendMfaToken = () => { return useMutation<{}, {}, SendMfaTokenDTO>({ @@ -91,22 +91,16 @@ export const useSendMfaToken = () => { return data; } }); -} +}; -export const verifyMfaToken = async ({ - email, - mfaCode -}: { - email: string; - mfaCode: string; -}) => { +export const verifyMfaToken = async ({ email, mfaCode }: { email: string; mfaCode: string }) => { const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", { email, mfaToken: mfaCode }); return data; -} +}; export const useVerifyMfaToken = () => { return useMutation({ @@ -117,87 +111,67 @@ export const useVerifyMfaToken = () => { }); } }); -} +}; export const verifySignupInvite = async (details: VerifySignupInviteDTO) => { const { data } = await apiRequest.post("/api/v1/invite-org/verify", details); return data; -} +}; export const useSendVerificationEmail = () => { return useMutation({ - mutationFn: async ({ - email - }: { - email: string; - }) => { + mutationFn: async ({ email }: { email: string }) => { const { data } = await apiRequest.post("/api/v1/signup/email/signup", { email }); - + return data; } }); -} +}; export const useVerifyEmailVerificationCode = () => { return useMutation({ - mutationFn: async ({ - email, - code - }: { - email: string; - code: string; - }) => { + mutationFn: async ({ email, code }: { email: string; code: string }) => { const { data } = await apiRequest.post("/api/v1/signup/email/verify", { email, code }); - + return data; } }); -} +}; export const useSendPasswordResetEmail = () => { return useMutation({ - mutationFn: async ({ - email - }: { - email: string; - }) => { + mutationFn: async ({ email }: { email: string }) => { const { data } = await apiRequest.post("/api/v1/password/email/password-reset", { email }); - + return data; } }); -} +}; export const useVerifyPasswordResetCode = () => { return useMutation({ - mutationFn: async ({ - email, - code - }: { - email: string; - code: string; - }) => { + mutationFn: async ({ email, code }: { email: string; code: string }) => { const { data } = await apiRequest.post("/api/v1/password/email/password-reset-verify", { email, code }); - + return data; } }); -} +}; export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => { const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details); return data; -} +}; export const getBackupEncryptedPrivateKey = async ({ verificationToken @@ -207,37 +181,41 @@ export const getBackupEncryptedPrivateKey = async ({ Authorization: `Bearer ${verificationToken}` } }); - + return data.backupPrivateKey; -} +}; export const useResetPassword = () => { return useMutation({ mutationFn: async (details: ResetPasswordDTO) => { - const { data } = await apiRequest.post("/api/v1/password/password-reset", { - protectedKey: details.protectedKey, - protectedKeyIV: details.protectedKeyIV, - protectedKeyTag: details.protectedKeyTag, - encryptedPrivateKey: details.encryptedPrivateKey, - encryptedPrivateKeyIV: details.encryptedPrivateKeyIV, - encryptedPrivateKeyTag: details.encryptedPrivateKeyTag, - salt: details.salt, - verifier: details.verifier - }, { - headers: { - Authorization: `Bearer ${details.verificationToken}` + const { data } = await apiRequest.post( + "/api/v1/password/password-reset", + { + protectedKey: details.protectedKey, + protectedKeyIV: details.protectedKeyIV, + protectedKeyTag: details.protectedKeyTag, + encryptedPrivateKey: details.encryptedPrivateKey, + encryptedPrivateKeyIV: details.encryptedPrivateKeyIV, + encryptedPrivateKeyTag: details.encryptedPrivateKeyTag, + salt: details.salt, + verifier: details.verifier + }, + { + headers: { + Authorization: `Bearer ${details.verificationToken}` + } } - }); - + ); + return data; } }); -} +}; export const changePassword = async (details: ChangePasswordDTO) => { const { data } = await apiRequest.post("/api/v1/password/change-password", details); return data; -} +}; export const useChangePassword = () => { // note: use after srp1 @@ -246,7 +224,7 @@ export const useChangePassword = () => { return changePassword(details); } }); -} +}; // Refresh token is set as cookie when logged in // Using that we fetch the auth bearer token needed for auth calls @@ -263,11 +241,3 @@ export const useGetAuthToken = () => onSuccess: (data) => setAuthToken(data.token), retry: 0 }); - -const fetchCommonPasswords = async () => { - const { data } = await apiRequest.get("/api/v1/auth/common-passwords"); - return data || []; -}; - -export const useGetCommonPasswords = () => - useQuery({ queryKey: authKeys.commonPasswords, queryFn: fetchCommonPasswords }); \ No newline at end of file diff --git a/frontend/src/pages/password-reset.tsx b/frontend/src/pages/password-reset.tsx index b38f67c01..0f63ef4af 100644 --- a/frontend/src/pages/password-reset.tsx +++ b/frontend/src/pages/password-reset.tsx @@ -10,9 +10,9 @@ import queryString from "query-string"; import Button from "@app/components/basic/buttons/Button"; import InputField from "@app/components/basic/InputField"; -import passwordCheck from "@app/components/utilities/checks/PasswordCheck"; +import passwordCheck from "@app/components/utilities/checks/password/PasswordCheck"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; -import { useResetPassword,useVerifyPasswordResetCode } from "@app/hooks/api"; +import { useResetPassword, useVerifyPasswordResetCode } from "@app/hooks/api"; import { getBackupEncryptedPrivateKey } from "@app/hooks/api/auth/queries"; import { deriveArgonKey } from "../components/utilities/cryptography/crypto"; @@ -28,25 +28,30 @@ export default function PasswordReset() { const [privateKey, setPrivateKey] = useState(""); const [newPassword, setNewPassword] = useState(""); const [backupKeyError, setBackupKeyError] = useState(false); - const [passwordErrorLength, setPasswordErrorLength] = useState(false); - const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); - const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); + const [passwordErrorTooShort, setPasswordErrorTooShort] = useState(false); + const [passwordErrorTooLong, setPasswordErrorTooLong] = useState(false); + const [passwordErrorNoLetterChar, setPasswordErrorNoLetterChar] = useState(false); + const [passwordErrorNoNumOrSpecialChar, setPasswordErrorNoNumOrSpecialChar] = useState(false); + const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false); + const [passwordErrorEscapeChar, setPasswordErrorEscapeChar] = useState(false); + const [passwordErrorLowEntropy, setPasswordErrorLowEntropy] = useState(false); + const [passwordErrorBreached, setPasswordErrorBreached] = useState(false); const router = useRouter(); const { mutateAsync: verifyPasswordResetCodeMutateAsync } = useVerifyPasswordResetCode(); const { mutateAsync: resetPasswordMutateAsync } = useResetPassword(); - + const parsedUrl = queryString.parse(router.asPath.split("?")[1]); const token = parsedUrl.token as string; const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); - // Unencrypt the private key with a backup key + // Decrypt the private key with a backup key const getEncryptedKeyHandler = async (e: FormEvent) => { e.preventDefault(); try { const result = await getBackupEncryptedPrivateKey({ verificationToken }); - + setPrivateKey( Aes256Gcm.decrypt({ ciphertext: result.encryptedPrivateKey, @@ -56,7 +61,7 @@ export default function PasswordReset() { }) ); setStep(3); - } catch(err) { + } catch (err) { console.error(err); setBackupKeyError(true); } @@ -65,12 +70,16 @@ export default function PasswordReset() { // If everything is correct, reset the password const resetPasswordHandler = async (e: FormEvent) => { e.preventDefault(); - const errorCheck = passwordCheck({ + const errorCheck = await passwordCheck({ password: newPassword, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - errorCheck: false + setPasswordErrorTooShort, + setPasswordErrorTooLong, + setPasswordErrorNoLetterChar, + setPasswordErrorNoNumOrSpecialChar, + setPasswordErrorRepeatedChar, + setPasswordErrorEscapeChar, + setPasswordErrorLowEntropy, + setPasswordErrorBreached }); if (!errorCheck) { @@ -127,10 +136,10 @@ export default function PasswordReset() { verifier: result.verifier, verificationToken }); - + router.push("/login"); - setLoading(false) + setLoading(false); }); } ); @@ -169,13 +178,17 @@ export default function PasswordReset() { // Input backup key const stepInputBackupKey = ( -
+

Enter your backup key

-
-

- You can find it in your emergency kit. You had to download the emergency kit during signup. +

+

+ You can find it in your emergency kit. You had to download the emergency kit during + signup.

@@ -192,12 +205,7 @@ export default function PasswordReset() {
-
@@ -205,13 +213,16 @@ export default function PasswordReset() { // Enter new password const stepEnterNewPassword = ( -
+

Enter new password

- Make sure you save it somewhere save. + Make sure you save it somewhere safe.

@@ -221,55 +232,141 @@ export default function PasswordReset() { setNewPassword(password); passwordCheck({ password, - setPasswordErrorLength, - setPasswordErrorNumber, - setPasswordErrorLowerCase, - errorCheck: false + setPasswordErrorTooShort, + setPasswordErrorTooLong, + setPasswordErrorNoLetterChar, + setPasswordErrorNoNumOrSpecialChar, + setPasswordErrorRepeatedChar, + setPasswordErrorEscapeChar, + setPasswordErrorLowEntropy, + setPasswordErrorBreached }); }} type="password" value={newPassword} isRequired - error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber} + error={ + passwordErrorTooShort && + passwordErrorTooLong && + passwordErrorNoLetterChar && + passwordErrorNoNumOrSpecialChar && + passwordErrorRepeatedChar && + passwordErrorEscapeChar && + passwordErrorLowEntropy && + passwordErrorBreached + } autoComplete="new-password" id="new-password" />
- {passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? ( + {passwordErrorTooShort || + passwordErrorTooLong || + passwordErrorNoLetterChar || + passwordErrorNoNumOrSpecialChar || + passwordErrorRepeatedChar || + passwordErrorEscapeChar || + passwordErrorLowEntropy || + passwordErrorBreached ? (
-
Password should contain at least:
+
Password should contain:
- {passwordErrorLength ? ( + {passwordErrorTooShort ? ( ) : ( )} -
- 14 characters +
+ at least 14 characters
- {passwordErrorLowerCase ? ( + {passwordErrorTooLong ? ( + + ) : ( + + )} +
+ at most 100 characters +
+
+
+ {passwordErrorNoLetterChar ? ( ) : ( )}
- 1 lowercase character + at least 1 letter character
- {passwordErrorNumber ? ( + {passwordErrorNoNumOrSpecialChar ? ( ) : ( )} -
- 1 number +
+ at least 1 number or special character
+
+ {passwordErrorRepeatedChar ? ( + + ) : ( + + )} +
+ at most 3 repeated, consecutive characters +
+
+
+ {passwordErrorEscapeChar ? ( + + ) : ( + + )} +
+ No escape characters allowed. +
+
+
+ {passwordErrorLowEntropy ? ( + + ) : ( + + )} +
+ Password contains sensitive data. +
+
+
+ {passwordErrorBreached ? ( + + ) : ( + + )} +
+ Password was found in a data breach. +
+
) : (
diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 9b1fe990f..0784806c8 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -16,36 +16,30 @@ import { encodeBase64 } from "tweetnacl-util"; import Button from "@app/components/basic/buttons/Button"; import InputField from "@app/components/basic/InputField"; -import checkPassword from "@app/components/utilities/checks/checkPassword"; +import checkPassword from "@app/components/utilities/checks/password/checkPassword"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; -import { - useGetCommonPasswords -} from "@app/hooks/api"; -import { - completeAccountSignupInvite, - verifySignupInvite -} from "@app/hooks/api/auth/queries"; +import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; // eslint-disable-next-line new-cap const client = new jsrp.client(); type Errors = { - length?: string, - upperCase?: string, - lowerCase?: string, - number?: string, - specialChar?: string, - repeatedChar?: string, + tooShort?: string; + tooLong?: string; + noLetterChar?: string; + noNumOrSpecialChar?: string; + repeatedChar?: string; + escapeChar?: string; + lowEntropy?: string; + breached?: string; }; export default function SignupInvite() { - const { data: commonPasswords } = useGetCommonPasswords(); - const [password, setPassword] = useState(""); const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); @@ -79,10 +73,9 @@ export default function SignupInvite() { } else { setLastNameError(false); } - - errorCheck = checkPassword({ + + errorCheck = await checkPassword({ password, - commonPasswords, setErrors }); @@ -116,7 +109,7 @@ export default function SignupInvite() { if (!derivedKey) throw new Error("Failed to derive key from password"); const key = crypto.randomBytes(32); - + // create encrypted private key by encrypting the private // key with the symmetric key [key] const { @@ -127,7 +120,7 @@ export default function SignupInvite() { text: privateKey, secret: key }); - + // create the protected key by encrypting the symmetric key // [key] with the derived key const { @@ -138,10 +131,8 @@ export default function SignupInvite() { text: key.toString("hex"), secret: Buffer.from(derivedKey.hash) }); - - const { - token: jwtToken - } = await completeAccountSignupInvite({ + + const { token: jwtToken } = await completeAccountSignupInvite({ email, firstName, lastName, @@ -155,20 +146,20 @@ export default function SignupInvite() { salt: result.salt, verifier: result.verifier }); - + // unset temporary signup JWT token and set JWT token SecurityClient.setSignupToken(""); SecurityClient.setToken(jwtToken); saveTokenToLocalStorage({ - publicKey, - encryptedPrivateKey, - iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag, - privateKey + publicKey, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag, + privateKey }); - const userOrgs = await fetchOrganizations(); + const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]._id; localStorage.setItem("orgData.id", orgId); @@ -188,12 +179,12 @@ export default function SignupInvite() { // Step 4 of the sign up process (download the emergency kit pdf) const stepConfirmEmail = ( -
-

+

+

Confirm your email

verify email -
+
- - ); -} \ No newline at end of file + ); + } + + return null; + })} +
+ )} + + + ); +}; diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 65be231b9..23353d7d8 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -1,4 +1,3 @@ - import crypto from "crypto"; import React, { useEffect, useState } from "react"; @@ -10,13 +9,12 @@ import nacl from "tweetnacl"; import { encodeBase64 } from "tweetnacl-util"; import InputField from "@app/components/basic/InputField"; -import checkPassword from "@app/components/utilities/checks/checkPassword"; +import checkPassword from "@app/components/utilities/checks/password/checkPassword"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, Input } from "@app/components/v2"; -import { useGetCommonPasswords } from "@app/hooks/api"; import { completeAccountSignup } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import ProjectService from "@app/services/ProjectService"; @@ -25,22 +23,24 @@ import ProjectService from "@app/services/ProjectService"; const client = new jsrp.client(); type Props = { - setStep: (step: number) => void; - email: string; - password: string; - setPassword: (value: string) => void; - name: string; - providerOrganizationName: string; - providerAuthToken?: string; -} + setStep: (step: number) => void; + email: string; + password: string; + setPassword: (value: string) => void; + name: string; + providerOrganizationName: string; + providerAuthToken?: string; +}; type Errors = { - length?: string, - upperCase?: string, - lowerCase?: string, - number?: string, - specialChar?: string, - repeatedChar?: string, + tooShort?: string; + tooLong?: string; + noLetterChar?: string; + noNumOrSpecialChar?: string; + repeatedChar?: string; + escapeChar?: string; + lowEntropy?: string; + breached?: string; }; /** @@ -63,9 +63,8 @@ export const UserInfoSSOStep = ({ password, setPassword, setStep, - providerAuthToken, + providerAuthToken }: Props) => { - const { data: commonPasswords } = useGetCommonPasswords(); const [nameError, setNameError] = useState(false); const [organizationName, setOrganizationName] = useState(""); const [organizationNameError, setOrganizationNameError] = useState(false); @@ -97,10 +96,9 @@ export const UserInfoSSOStep = ({ } else { setOrganizationNameError(false); } - - errorCheck = checkPassword({ + + errorCheck = await checkPassword({ password, - commonPasswords, setErrors }); @@ -210,15 +208,17 @@ export const UserInfoSSOStep = ({ setIsLoading(false); } }; - + return ( -
-

+

+

{t("signup.step3-message")}

-
-
-

Your Name

+
+
+

+ Your Name +

- {nameError &&

Please, specify your name

} + {nameError && ( +

+ Please, specify your name +

+ )}
{providerOrganizationName === undefined && ( -
-

Organization Name

+
+

+ Organization Name +

- {organizationNameError &&

Please, specify your organization name

} + {organizationNameError && ( +

+ Please, specify your organization name +

+ )}
)} {providerOrganizationName === undefined && ( -
-

Where did you hear about us? (optional)

+
+

+ Where did you hear about us? (optional) +

setAttributionSource(e.target.value)} @@ -254,14 +266,13 @@ export const UserInfoSSOStep = ({ />
)} -
+
{ + onChangeHandler={async (pass: string) => { setPassword(pass); - checkPassword({ + await checkPassword({ password: pass, - commonPasswords, setErrors }); }} @@ -272,26 +283,27 @@ export const UserInfoSSOStep = ({ autoComplete="new-password" id="new-password" /> -
Infisical Password is used as part of the encryption mechanism so that even the authentication provider is not able to access your secrets.
+
+ + Infisical Password is used as part of the encryption mechanism so that even the + authentication provider is not able to access your secrets. +
{Object.keys(errors).length > 0 && (
-
{t("section.password.validate-base")}
+
+ {t("section.password.validate-base")} +
{Object.keys(errors).map((key) => { if (errors[key as keyof Errors]) { return ( -
+
-
-

- {errors[key as keyof Errors]} -

+

{errors[key as keyof Errors]}

); } @@ -301,21 +313,24 @@ export const UserInfoSSOStep = ({
)}
-
-
+
+
+ > + {" "} + {String(t("signup.signup"))}{" "} +
); -} +};