* chore(utils): copy helper functions from web/helpers Co-Authored-By: sriram@plane.so <sriram@plane.so> * chore(utils): bump version to 0.24.2 Co-Authored-By: sriram@plane.so <sriram@plane.so> * chore: bump root package version to 0.24.2 Co-Authored-By: sriram@plane.so <sriram@plane.so> * fix: remove duplicate function and simplify auth utils Co-Authored-By: sriram@plane.so <sriram@plane.so> * fix: improve HTML entity escaping in sanitizeHTML Co-Authored-By: sriram@plane.so <sriram@plane.so> * fix: version changes --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: sriram@plane.so <sriram@plane.so>
387 lines
14 KiB
TypeScript
387 lines
14 KiB
TypeScript
import { ReactNode } from "react";
|
|
import zxcvbn from "zxcvbn";
|
|
import {
|
|
E_PASSWORD_STRENGTH,
|
|
SPACE_PASSWORD_CRITERIA,
|
|
PASSWORD_MIN_LENGTH,
|
|
EErrorAlertType,
|
|
EAuthErrorCodes,
|
|
} from "@plane/constants";
|
|
|
|
/**
|
|
* @description Password strength levels
|
|
*/
|
|
export enum PasswordStrength {
|
|
EMPTY = "empty",
|
|
WEAK = "weak",
|
|
FAIR = "fair",
|
|
GOOD = "good",
|
|
STRONG = "strong",
|
|
}
|
|
|
|
/**
|
|
* @description Password strength criteria type
|
|
*/
|
|
export type PasswordCriterion = {
|
|
regex: RegExp;
|
|
description: string;
|
|
};
|
|
|
|
/**
|
|
* @description Password strength criteria
|
|
*/
|
|
export const PASSWORD_CRITERIA: PasswordCriterion[] = [
|
|
{ regex: /[a-z]/, description: "lowercase" },
|
|
{ regex: /[A-Z]/, description: "uppercase" },
|
|
{ regex: /[0-9]/, description: "number" },
|
|
{ regex: /[^a-zA-Z0-9]/, description: "special character" },
|
|
];
|
|
|
|
/**
|
|
* @description Checks if password meets all criteria
|
|
* @param {string} password - Password to check
|
|
* @returns {boolean} Whether password meets all criteria
|
|
*/
|
|
export const checkPasswordCriteria = (password: string): boolean =>
|
|
PASSWORD_CRITERIA.every((criterion) => criterion.regex.test(password));
|
|
|
|
/**
|
|
* @description Checks password strength against criteria
|
|
* @param {string} password - Password to check
|
|
* @returns {PasswordStrength} Password strength level
|
|
* @example
|
|
* checkPasswordStrength("abc") // returns PasswordStrength.WEAK
|
|
* checkPasswordStrength("Abc123!@#") // returns PasswordStrength.STRONG
|
|
*/
|
|
export const checkPasswordStrength = (password: string): PasswordStrength => {
|
|
if (!password || password.length === 0) return PasswordStrength.EMPTY;
|
|
if (password.length < PASSWORD_MIN_LENGTH) return PasswordStrength.WEAK;
|
|
|
|
const criteriaCount = PASSWORD_CRITERIA.filter((criterion) => criterion.regex.test(password)).length;
|
|
|
|
const zxcvbnScore = zxcvbn(password).score;
|
|
|
|
if (criteriaCount <= 1 || zxcvbnScore <= 1) return PasswordStrength.WEAK;
|
|
if (criteriaCount === 2 || zxcvbnScore === 2) return PasswordStrength.FAIR;
|
|
if (criteriaCount === 3 || zxcvbnScore === 3) return PasswordStrength.GOOD;
|
|
return PasswordStrength.STRONG;
|
|
};
|
|
|
|
export type TAuthErrorInfo = {
|
|
type: EErrorAlertType;
|
|
code: EAuthErrorCodes;
|
|
title: string;
|
|
message: ReactNode;
|
|
};
|
|
|
|
// Password strength check
|
|
export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => {
|
|
let passwordStrength: E_PASSWORD_STRENGTH = E_PASSWORD_STRENGTH.EMPTY;
|
|
|
|
if (!password || password === "" || password.length <= 0) {
|
|
return passwordStrength;
|
|
}
|
|
|
|
if (password.length >= PASSWORD_MIN_LENGTH) {
|
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
|
|
} else {
|
|
passwordStrength = E_PASSWORD_STRENGTH.LENGTH_NOT_VALID;
|
|
return passwordStrength;
|
|
}
|
|
|
|
const passwordCriteriaValidation = SPACE_PASSWORD_CRITERIA.map((criteria) =>
|
|
criteria.isCriteriaValid(password)
|
|
).every((criterion) => criterion);
|
|
const passwordStrengthScore = zxcvbn(password).score;
|
|
|
|
if (passwordCriteriaValidation === false || passwordStrengthScore <= 2) {
|
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
|
|
return passwordStrength;
|
|
}
|
|
|
|
if (passwordCriteriaValidation === true && passwordStrengthScore >= 3) {
|
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_VALID;
|
|
}
|
|
|
|
return passwordStrength;
|
|
};
|
|
|
|
// Error code messages
|
|
const errorCodeMessages: {
|
|
[key in EAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
|
|
} = {
|
|
// global
|
|
[EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: {
|
|
title: `Instance not configured`,
|
|
message: () => `Instance not configured. Please contact your administrator.`,
|
|
},
|
|
[EAuthErrorCodes.SIGNUP_DISABLED]: {
|
|
title: `Sign up disabled`,
|
|
message: () => `Sign up disabled. Please contact your administrator.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_PASSWORD]: {
|
|
title: `Invalid password`,
|
|
message: () => `Invalid password. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.SMTP_NOT_CONFIGURED]: {
|
|
title: `SMTP not configured`,
|
|
message: () => `SMTP not configured. Please contact your administrator.`,
|
|
},
|
|
// email check in both sign up and sign in
|
|
[EAuthErrorCodes.INVALID_EMAIL]: {
|
|
title: `Invalid email`,
|
|
message: () => `Invalid email. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.EMAIL_REQUIRED]: {
|
|
title: `Email required`,
|
|
message: () => `Email required. Please try again.`,
|
|
},
|
|
// sign up
|
|
[EAuthErrorCodes.USER_ALREADY_EXIST]: {
|
|
title: `User already exists`,
|
|
message: () => `Your account is already registered. Sign in now.`,
|
|
},
|
|
[EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: {
|
|
title: `Email and password required`,
|
|
message: () => `Email and password required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
|
|
title: `Authentication failed`,
|
|
message: () => `Authentication failed. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_EMAIL_SIGN_UP]: {
|
|
title: `Invalid email`,
|
|
message: () => `Invalid email. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
|
|
title: `Email and code required`,
|
|
message: () => `Email and code required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
|
|
title: `Invalid email`,
|
|
message: () => `Invalid email. Please try again.`,
|
|
},
|
|
// sign in
|
|
[EAuthErrorCodes.USER_ACCOUNT_DEACTIVATED]: {
|
|
title: `User account deactivated`,
|
|
message: () => `User account deactivated. Please contact administrator.`,
|
|
},
|
|
[EAuthErrorCodes.USER_DOES_NOT_EXIST]: {
|
|
title: `User does not exist`,
|
|
message: () => `No account found. Create one to get started.`,
|
|
},
|
|
[EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
|
|
title: `Email and password required`,
|
|
message: () => `Email and password required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
|
|
title: `Authentication failed`,
|
|
message: () => `Authentication failed. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_EMAIL_SIGN_IN]: {
|
|
title: `Invalid email`,
|
|
message: () => `Invalid email. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
|
|
title: `Email and code required`,
|
|
message: () => `Email and code required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
|
|
title: `Invalid email`,
|
|
message: () => `Invalid email. Please try again.`,
|
|
},
|
|
// Both Sign in and Sign up
|
|
[EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_IN]: {
|
|
title: `Authentication failed`,
|
|
message: () => `Invalid magic code. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_UP]: {
|
|
title: `Authentication failed`,
|
|
message: () => `Invalid magic code. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_IN]: {
|
|
title: `Expired magic code`,
|
|
message: () => `Expired magic code. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_UP]: {
|
|
title: `Expired magic code`,
|
|
message: () => `Expired magic code. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN]: {
|
|
title: `Expired magic code`,
|
|
message: () => `Expired magic code. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP]: {
|
|
title: `Expired magic code`,
|
|
message: () => `Expired magic code. Please try again.`,
|
|
},
|
|
// Oauth
|
|
[EAuthErrorCodes.OAUTH_NOT_CONFIGURED]: {
|
|
title: `OAuth not configured`,
|
|
message: () => `OAuth not configured. Please contact your administrator.`,
|
|
},
|
|
[EAuthErrorCodes.GOOGLE_NOT_CONFIGURED]: {
|
|
title: `Google not configured`,
|
|
message: () => `Google not configured. Please contact your administrator.`,
|
|
},
|
|
[EAuthErrorCodes.GITHUB_NOT_CONFIGURED]: {
|
|
title: `GitHub not configured`,
|
|
message: () => `GitHub not configured. Please contact your administrator.`,
|
|
},
|
|
[EAuthErrorCodes.GITLAB_NOT_CONFIGURED]: {
|
|
title: `GitLab not configured`,
|
|
message: () => `GitLab not configured. Please contact your administrator.`,
|
|
},
|
|
[EAuthErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
|
|
title: `Google OAuth provider error`,
|
|
message: () => `Google OAuth provider error. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
|
|
title: `GitHub OAuth provider error`,
|
|
message: () => `GitHub OAuth provider error. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.GITLAB_OAUTH_PROVIDER_ERROR]: {
|
|
title: `GitLab OAuth provider error`,
|
|
message: () => `GitLab OAuth provider error. Please try again.`,
|
|
},
|
|
// Reset Password
|
|
[EAuthErrorCodes.INVALID_PASSWORD_TOKEN]: {
|
|
title: `Invalid password token`,
|
|
message: () => `Invalid password token. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
|
|
title: `Expired password token`,
|
|
message: () => `Expired password token. Please try again.`,
|
|
},
|
|
// Change password
|
|
[EAuthErrorCodes.MISSING_PASSWORD]: {
|
|
title: `Password required`,
|
|
message: () => `Password required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INCORRECT_OLD_PASSWORD]: {
|
|
title: `Incorrect old password`,
|
|
message: () => `Incorrect old password. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_NEW_PASSWORD]: {
|
|
title: `Invalid new password`,
|
|
message: () => `Invalid new password. Please try again.`,
|
|
},
|
|
// set password
|
|
[EAuthErrorCodes.PASSWORD_ALREADY_SET]: {
|
|
title: `Password already set`,
|
|
message: () => `Password already set. Please try again.`,
|
|
},
|
|
// admin
|
|
[EAuthErrorCodes.ADMIN_ALREADY_EXIST]: {
|
|
title: `Admin already exists`,
|
|
message: () => `Admin already exists. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
|
title: `Email, password and first name required`,
|
|
message: () => `Email, password and first name required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_ADMIN_EMAIL]: {
|
|
title: `Invalid admin email`,
|
|
message: () => `Invalid admin email. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
|
title: `Invalid admin password`,
|
|
message: () => `Invalid admin password. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
|
title: `Email and password required`,
|
|
message: () => `Email and password required. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
|
title: `Authentication failed`,
|
|
message: () => `Authentication failed. Please try again.`,
|
|
},
|
|
[EAuthErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
|
title: `Admin user already exists`,
|
|
message: () => `Admin user already exists. Sign in now.`,
|
|
},
|
|
[EAuthErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
|
title: `Admin user does not exist`,
|
|
message: () => `Admin user does not exist. Sign in now.`,
|
|
},
|
|
[EAuthErrorCodes.MAGIC_LINK_LOGIN_DISABLED]: {
|
|
title: `Magic link login disabled`,
|
|
message: () => `Magic link login is disabled. Please use password to login.`,
|
|
},
|
|
[EAuthErrorCodes.PASSWORD_LOGIN_DISABLED]: {
|
|
title: `Password login disabled`,
|
|
message: () => `Password login is disabled. Please use magic link to login.`,
|
|
},
|
|
[EAuthErrorCodes.ADMIN_USER_DEACTIVATED]: {
|
|
title: `Admin user deactivated`,
|
|
message: () => `Admin user account has been deactivated. Please contact administrator.`,
|
|
},
|
|
[EAuthErrorCodes.RATE_LIMIT_EXCEEDED]: {
|
|
title: `Rate limit exceeded`,
|
|
message: () => `Too many requests. Please try again later.`,
|
|
},
|
|
};
|
|
|
|
// Error handler
|
|
export const authErrorHandler = (
|
|
errorCode: EAuthErrorCodes,
|
|
email?: string | undefined
|
|
): TAuthErrorInfo | undefined => {
|
|
const bannerAlertErrorCodes = [
|
|
EAuthErrorCodes.INSTANCE_NOT_CONFIGURED,
|
|
EAuthErrorCodes.INVALID_EMAIL,
|
|
EAuthErrorCodes.EMAIL_REQUIRED,
|
|
EAuthErrorCodes.SIGNUP_DISABLED,
|
|
EAuthErrorCodes.INVALID_PASSWORD,
|
|
EAuthErrorCodes.SMTP_NOT_CONFIGURED,
|
|
EAuthErrorCodes.USER_ALREADY_EXIST,
|
|
EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
|
|
EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
|
|
EAuthErrorCodes.INVALID_EMAIL_SIGN_UP,
|
|
EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP,
|
|
EAuthErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
|
|
EAuthErrorCodes.USER_DOES_NOT_EXIST,
|
|
EAuthErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
|
|
EAuthErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
|
|
EAuthErrorCodes.INVALID_EMAIL_SIGN_IN,
|
|
EAuthErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
|
|
EAuthErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
|
|
EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_IN,
|
|
EAuthErrorCodes.INVALID_MAGIC_CODE_SIGN_UP,
|
|
EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_IN,
|
|
EAuthErrorCodes.EXPIRED_MAGIC_CODE_SIGN_UP,
|
|
EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN,
|
|
EAuthErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP,
|
|
EAuthErrorCodes.OAUTH_NOT_CONFIGURED,
|
|
EAuthErrorCodes.GOOGLE_NOT_CONFIGURED,
|
|
EAuthErrorCodes.GITHUB_NOT_CONFIGURED,
|
|
EAuthErrorCodes.GITLAB_NOT_CONFIGURED,
|
|
EAuthErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR,
|
|
EAuthErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR,
|
|
EAuthErrorCodes.GITLAB_OAUTH_PROVIDER_ERROR,
|
|
EAuthErrorCodes.INVALID_PASSWORD_TOKEN,
|
|
EAuthErrorCodes.EXPIRED_PASSWORD_TOKEN,
|
|
EAuthErrorCodes.INCORRECT_OLD_PASSWORD,
|
|
EAuthErrorCodes.INVALID_NEW_PASSWORD,
|
|
EAuthErrorCodes.PASSWORD_ALREADY_SET,
|
|
EAuthErrorCodes.ADMIN_ALREADY_EXIST,
|
|
EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
|
EAuthErrorCodes.INVALID_ADMIN_EMAIL,
|
|
EAuthErrorCodes.INVALID_ADMIN_PASSWORD,
|
|
EAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
|
|
EAuthErrorCodes.ADMIN_AUTHENTICATION_FAILED,
|
|
EAuthErrorCodes.ADMIN_USER_ALREADY_EXIST,
|
|
EAuthErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
|
|
EAuthErrorCodes.USER_ACCOUNT_DEACTIVATED,
|
|
];
|
|
|
|
if (bannerAlertErrorCodes.includes(errorCode))
|
|
return {
|
|
type: EErrorAlertType.BANNER_ALERT,
|
|
code: errorCode,
|
|
title: errorCodeMessages[errorCode]?.title || "Error",
|
|
message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.",
|
|
};
|
|
|
|
return undefined;
|
|
};
|