Skip to content

Commit

Permalink
chore: copy helper functions from admin and space into @plane/utils (#…
Browse files Browse the repository at this point in the history
…6256)

* chore: copy helper functions from space to @plane/utils

Co-Authored-By: [email protected] <[email protected]>

* refactor: move enums from utils/auth.ts to @plane/constants/auth.ts

Co-Authored-By: [email protected] <[email protected]>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: [email protected] <[email protected]>
  • Loading branch information
1 parent 043f4ea commit 9f5def3
Show file tree
Hide file tree
Showing 11 changed files with 651 additions and 3 deletions.
12 changes: 10 additions & 2 deletions packages/constants/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export enum EAuthPageTypes {
AUTHENTICATED = "AUTHENTICATED",
}

export enum EPageTypes {
INIT = "INIT",
PUBLIC = "PUBLIC",
NON_AUTHENTICATED = "NON_AUTHENTICATED",
ONBOARDING = "ONBOARDING",
AUTHENTICATED = "AUTHENTICATED",
}

export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
Expand All @@ -50,9 +58,9 @@ export enum EAuthSteps {
UNIQUE_CODE = "UNIQUE_CODE",
}

// TODO: remove this
export enum EErrorAlertType {
BANNER_ALERT = "BANNER_ALERT",
TOAST_ALERT = "TOAST_ALERT",
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
INLINE_EMAIL = "INLINE_EMAIL",
INLINE_PASSWORD = "INLINE_PASSWORD",
Expand Down Expand Up @@ -127,7 +135,7 @@ export enum EAuthErrorCodes {
INCORRECT_OLD_PASSWORD = "5135",
MISSING_PASSWORD = "5138",
INVALID_NEW_PASSWORD = "5140",
// set passowrd
// set password
PASSWORD_ALREADY_SET = "5145",
// Admin
ADMIN_ALREADY_EXIST = "5150",
Expand Down
291 changes: 291 additions & 0 deletions packages/utils/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { ReactNode } from "react";
import zxcvbn from "zxcvbn";
import { E_PASSWORD_STRENGTH, PASSWORD_CRITERIA, PASSWORD_MIN_LENGTH } from "@plane/constants";

import { EPageTypes, EErrorAlertType, EAuthErrorCodes } from "@plane/constants";

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;

Expand Down Expand Up @@ -31,3 +42,283 @@ export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => {

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: (email = undefined) => `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;
};
3 changes: 3 additions & 0 deletions packages/utils/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

// Support email can be configured by the application
export const getSupportEmail = (defaultEmail: string = ""): string => defaultEmail;

export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
46 changes: 46 additions & 0 deletions packages/utils/src/datetime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { format, isValid } from "date-fns";

/**
* This method returns a date from string of type yyyy-mm-dd
* This method is recommended to use instead of new Date() as this does not introduce any timezone offsets
* @param date
* @returns date or undefined
*/
export const getDate = (date: string | Date | undefined | null): Date | undefined => {
try {
if (!date || date === "") return;

if (typeof date !== "string" && !(date instanceof String)) return date;

const [yearString, monthString, dayString] = date.substring(0, 10).split("-");
const year = parseInt(yearString);
const month = parseInt(monthString);
const day = parseInt(dayString);
// Using Number.isInteger instead of lodash's isNumber for better specificity and no external dependency
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return;

return new Date(year, month - 1, day);
} catch (e) {
return undefined;
}
};

/**
* @returns {string | null} formatted date in the format of MMM dd, yyyy
* @description Returns date in the formatted format
* @param {Date | string} date
* @example renderFormattedDate("2024-01-01") // Jan 01, 2024
*/
export const renderFormattedDate = (date: string | Date | undefined | null): string | null => {
// Parse the date to check if it is valid
const parsedDate = getDate(date);
// return if undefined
if (!parsedDate) return null;
// Check if the parsed date is valid before formatting
if (!isValid(parsedDate)) return null; // Return null for invalid dates
// Format the date in format (MMM dd, yyyy)
const formattedDate = format(parsedDate, "MMM dd, yyyy");
return formattedDate;
};

// Note: timeAgo function was incomplete in the original file, so it has been omitted
Loading

0 comments on commit 9f5def3

Please sign in to comment.