mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-14 11:50:31 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
883c5dcb41 | ||
|
|
be10b8934d | ||
|
|
ce38c71fa7 | ||
|
|
1162fbc7c3 | ||
|
|
18b9e25f2b | ||
|
|
dd26bdc482 | ||
|
|
ad3c9ebfe9 |
@@ -32,3 +32,8 @@ CONTACT_US_MAIL=support@bigcapital.ly
|
||||
# Agendash
|
||||
AGENDASH_AUTH_USER=agendash
|
||||
AGENDASH_AUTH_PASSWORD=123123
|
||||
|
||||
# Sign-up restrictions
|
||||
SIGNUP_DISABLED=true
|
||||
SIGNUP_ALLOWED_DOMAINS=
|
||||
SIGNUP_ALLOWED_EMAILS=
|
||||
@@ -72,6 +72,11 @@ services:
|
||||
- AGENDASH_AUTH_USER=${AGENDASH_AUTH_USER}
|
||||
- AGENDASH_AUTH_PASSWORD=${AGENDASH_AUTH_PASSWORD}
|
||||
|
||||
# Sign-up restrictions
|
||||
- SIGNUP_DISABLED=${SIGNUP_DISABLED}
|
||||
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
|
||||
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
|
||||
|
||||
database_migration:
|
||||
container_name: bigcapital-database-migration
|
||||
build:
|
||||
|
||||
@@ -34,7 +34,11 @@ ARG MAIL_HOST= \
|
||||
BASE_URL= \
|
||||
# Agendash
|
||||
AGENDASH_AUTH_USER=agendash \
|
||||
AGENDASH_AUTH_PASSWORD=123123
|
||||
AGENDASH_AUTH_PASSWORD=123123 \
|
||||
# Sign-up restriction
|
||||
SIGNUP_DISABLED= \
|
||||
SIGNUP_ALLOWED_DOMAINS= \
|
||||
SIGNUP_ALLOWED_EMAILS=
|
||||
|
||||
ENV MAIL_HOST=$MAIL_HOST \
|
||||
MAIL_USERNAME=$MAIL_USERNAME \
|
||||
@@ -68,7 +72,11 @@ ENV MAIL_HOST=$MAIL_HOST \
|
||||
# MongoDB
|
||||
MONGODB_DATABASE_URL=$MONGODB_DATABASE_URL \
|
||||
# Application
|
||||
BASE_URL=$BASE_URL
|
||||
BASE_URL=$BASE_URL \
|
||||
# Sign-up restriction
|
||||
SIGNUP_DISABLED=$SIGNUP_DISABLED \
|
||||
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
||||
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
||||
|
||||
# Create app directory.
|
||||
WORKDIR /app
|
||||
|
||||
@@ -49,6 +49,7 @@ export default class AuthenticationController extends BaseController {
|
||||
asyncMiddleware(this.resetPassword.bind(this)),
|
||||
this.handlerErrors
|
||||
);
|
||||
router.get('/meta', asyncMiddleware(this.getAuthMeta.bind(this)));
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -207,6 +208,23 @@ export default class AuthenticationController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the authentication meta for SPA.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
* @returns {Response|void}
|
||||
*/
|
||||
private async getAuthMeta(req: Request, res: Response, next: Function) {
|
||||
try {
|
||||
const meta = await this.authApplication.getAuthMeta();
|
||||
|
||||
return res.status(200).send({ meta });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the service errors.
|
||||
*/
|
||||
@@ -247,6 +265,30 @@ export default class AuthenticationController extends BaseController {
|
||||
errors: [{ type: 'EMAIL.EXISTS', code: 600 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'SIGNUP_RESTRICTED') {
|
||||
return res.status(400).send({
|
||||
errors: [
|
||||
{
|
||||
type: 'SIGNUP_RESTRICTED',
|
||||
message:
|
||||
'Sign-up is restricted no one can sign-up to the system.',
|
||||
code: 700,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'SIGNUP_RESTRICTED_NOT_ALLOWED') {
|
||||
return res.status(400).send({
|
||||
errors: [
|
||||
{
|
||||
type: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
|
||||
message:
|
||||
'Sign-up is restricted the given email address is not allowed to sign-up.',
|
||||
code: 710,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -146,6 +147,19 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign-up restrictions
|
||||
*/
|
||||
signupRestrictions: {
|
||||
disabled: parseBoolean<boolean>(process.env.SIGNUP_DISABLED, false),
|
||||
allowedDomains: castCommaListEnvVarToArray(
|
||||
process.env.SIGNUP_ALLOWED_DOMAINS
|
||||
),
|
||||
allowedEmails: castCommaListEnvVarToArray(
|
||||
process.env.SIGNUP_ALLOWED_EMAILS
|
||||
),
|
||||
},
|
||||
|
||||
/**
|
||||
* Puppeteer remote browserless connection.
|
||||
*/
|
||||
|
||||
@@ -74,4 +74,8 @@ export interface IAuthSendingResetPassword {
|
||||
export interface IAuthSendedResetPassword {
|
||||
user: ISystemUser,
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface IAuthGetMetaPOJO {
|
||||
signupDisabled: boolean;
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Service, Inject, Container } from 'typedi';
|
||||
import { IRegisterDTO, ISystemUser, IPasswordReset } from '@/interfaces';
|
||||
import {
|
||||
IRegisterDTO,
|
||||
ISystemUser,
|
||||
IPasswordReset,
|
||||
IAuthGetMetaPOJO,
|
||||
} from '@/interfaces';
|
||||
import { AuthSigninService } from './AuthSignin';
|
||||
import { AuthSignupService } from './AuthSignup';
|
||||
import { AuthSendResetPassword } from './AuthSendResetPassword';
|
||||
import { GetAuthMeta } from './GetAuthMeta';
|
||||
|
||||
@Service()
|
||||
export default class AuthenticationApplication {
|
||||
@@ -15,6 +21,9 @@ export default class AuthenticationApplication {
|
||||
@Inject()
|
||||
private authResetPasswordService: AuthSendResetPassword;
|
||||
|
||||
@Inject()
|
||||
private authGetMeta: GetAuthMeta;
|
||||
|
||||
/**
|
||||
* Signin and generates JWT token.
|
||||
* @throws {ServiceError}
|
||||
@@ -53,4 +62,12 @@ export default class AuthenticationApplication {
|
||||
public async resetPassword(token: string, password: string): Promise<void> {
|
||||
return this.authResetPasswordService.resetPassword(token, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the authentication meta for SPA.
|
||||
* @returns {Promise<IAuthGetMetaPOJO>}
|
||||
*/
|
||||
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
||||
return this.authGetMeta.getAuthMeta();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { omit } from 'lodash';
|
||||
import { isEmpty, omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import TenantsManagerService from '../Tenancy/TenantsManager';
|
||||
import events from '@/subscribers/events';
|
||||
import { hashPassword } from '@/utils';
|
||||
import config from '@/config';
|
||||
|
||||
export class AuthSignupService {
|
||||
@Inject()
|
||||
@@ -33,6 +34,9 @@ export class AuthSignupService {
|
||||
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
|
||||
// Validates the signup disable restrictions.
|
||||
await this.validateSignupRestrictions(signupDTO.email);
|
||||
|
||||
// Validates the given email uniqiness.
|
||||
await this.validateEmailUniqiness(signupDTO.email);
|
||||
|
||||
@@ -74,4 +78,34 @@ export class AuthSignupService {
|
||||
throw new ServiceError(ERRORS.EMAIL_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sign-up disable restrictions.
|
||||
* @param {string} email
|
||||
*/
|
||||
private async validateSignupRestrictions(email: string) {
|
||||
// Can't continue if the signup is not disabled.
|
||||
if (!config.signupRestrictions.disabled) return;
|
||||
|
||||
// Validate the allowed email addresses and domains.
|
||||
if (
|
||||
!isEmpty(config.signupRestrictions.allowedEmails) ||
|
||||
!isEmpty(config.signupRestrictions.allowedDomains)
|
||||
) {
|
||||
const emailDomain = email.split('@').pop();
|
||||
const isAllowedEmail =
|
||||
config.signupRestrictions.allowedEmails.indexOf(email) !== -1;
|
||||
|
||||
const isAllowedDomain = config.signupRestrictions.allowedDomains.some(
|
||||
(domain) => emailDomain === domain
|
||||
);
|
||||
|
||||
if (!isAllowedEmail && !isAllowedDomain) {
|
||||
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED_NOT_ALLOWED);
|
||||
}
|
||||
// Throw error if the signup is disabled with no exceptions.
|
||||
} else {
|
||||
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
packages/server/src/services/Authentication/GetAuthMeta.ts
Normal file
16
packages/server/src/services/Authentication/GetAuthMeta.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Service } from 'typedi';
|
||||
import { IAuthGetMetaPOJO } from '@/interfaces';
|
||||
import config from '@/config';
|
||||
|
||||
@Service()
|
||||
export class GetAuthMeta {
|
||||
/**
|
||||
* Retrieves the authentication meta for SPA.
|
||||
* @returns {Promise<IAuthGetMetaPOJO>}
|
||||
*/
|
||||
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
||||
return {
|
||||
signupDisabled: config.signupRestrictions.disabled,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,6 @@ export const ERRORS = {
|
||||
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
||||
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
|
||||
EMAIL_EXISTS: 'EMAIL_EXISTS',
|
||||
SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
|
||||
SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED',
|
||||
};
|
||||
|
||||
@@ -467,6 +467,10 @@ const assocDepthLevelToObjectTree = (
|
||||
return objects;
|
||||
};
|
||||
|
||||
const castCommaListEnvVarToArray = (envVar: string): Array<string> => {
|
||||
return envVar ? envVar?.split(',')?.map(_.trim) : [];
|
||||
};
|
||||
|
||||
export {
|
||||
templateRender,
|
||||
accumSum,
|
||||
@@ -499,4 +503,5 @@ export {
|
||||
mergeObjectsBykey,
|
||||
nestedArrayToFlatten,
|
||||
assocDepthLevelToObjectTree,
|
||||
castCommaListEnvVarToArray
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext } from 'react';
|
||||
import { useAuthMetadata } from '@/hooks/query';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const AuthMetaBootContext = createContext();
|
||||
|
||||
/**
|
||||
* Boots the authentication page metadata.
|
||||
*/
|
||||
function AuthMetaBootProvider({ ...props }) {
|
||||
const { isLoading: isAuthMetaLoading, data: authMeta } = useAuthMetadata();
|
||||
|
||||
const state = {
|
||||
isAuthMetaLoading,
|
||||
signupDisabled: authMeta?.meta?.signup_disabled,
|
||||
};
|
||||
|
||||
if (isAuthMetaLoading) {
|
||||
return (
|
||||
<SpinnerRoot>
|
||||
<Spinner size={30} value={null} />
|
||||
</SpinnerRoot>
|
||||
);
|
||||
}
|
||||
return <AuthMetaBootContext.Provider value={state} {...props} />;
|
||||
}
|
||||
|
||||
const useAuthMetaBoot = () => React.useContext(AuthMetaBootContext);
|
||||
|
||||
export { AuthMetaBootContext, AuthMetaBootProvider, useAuthMetaBoot };
|
||||
|
||||
const SpinnerRoot = styled.div`
|
||||
margin-top: 5rem;
|
||||
`;
|
||||
@@ -10,12 +10,11 @@ import { Icon, FormattedMessage as T } from '@/components';
|
||||
import { useIsAuthenticated } from '@/hooks/state';
|
||||
|
||||
import '@/style/pages/Authentication/Auth.scss';
|
||||
import { AuthMetaBootProvider } from './AuthMetaBoot';
|
||||
|
||||
export function Authentication() {
|
||||
const to = { pathname: '/' };
|
||||
const location = useLocation();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const locationKey = location.pathname;
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Redirect to={to} />;
|
||||
@@ -28,30 +27,41 @@ export function Authentication() {
|
||||
<Icon icon="bigcapital" height={37} width={214} />
|
||||
</AuthLogo>
|
||||
|
||||
<TransitionGroup>
|
||||
<CSSTransition
|
||||
timeout={500}
|
||||
key={locationKey}
|
||||
classNames="authTransition"
|
||||
>
|
||||
<Switch>
|
||||
{authenticationRoutes.map((route, index) => (
|
||||
<Route
|
||||
key={index}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
component={route.component}
|
||||
/>
|
||||
))}
|
||||
</Switch>
|
||||
</CSSTransition>
|
||||
</TransitionGroup>
|
||||
<AuthMetaBootProvider>
|
||||
<AuthenticationRoutes />
|
||||
</AuthMetaBootProvider>
|
||||
</AuthInsider>
|
||||
</AuthPage>
|
||||
</BodyClassName>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthenticationRoutes() {
|
||||
const location = useLocation();
|
||||
const locationKey = location.pathname;
|
||||
|
||||
return (
|
||||
<TransitionGroup>
|
||||
<CSSTransition
|
||||
timeout={500}
|
||||
key={locationKey}
|
||||
classNames="authTransition"
|
||||
>
|
||||
<Switch>
|
||||
{authenticationRoutes.map((route, index) => (
|
||||
<Route
|
||||
key={index}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
component={route.component}
|
||||
/>
|
||||
))}
|
||||
</Switch>
|
||||
</CSSTransition>
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const AuthPage = styled.div``;
|
||||
const AuthInsider = styled.div`
|
||||
width: 384px;
|
||||
|
||||
@@ -14,11 +14,12 @@ import {
|
||||
AuthFooterLink,
|
||||
AuthInsiderCard,
|
||||
} from './_components';
|
||||
import { useAuthMetaBoot } from './AuthMetaBoot';
|
||||
|
||||
const initialValues = {
|
||||
crediential: '',
|
||||
password: '',
|
||||
keepLoggedIn: false
|
||||
keepLoggedIn: false,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,12 +65,15 @@ export default function Login() {
|
||||
}
|
||||
|
||||
function LoginFooterLinks() {
|
||||
const { signupDisabled } = useAuthMetaBoot();
|
||||
|
||||
return (
|
||||
<AuthFooterLinks>
|
||||
<AuthFooterLink>
|
||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||
</AuthFooterLink>
|
||||
|
||||
{!signupDisabled && (
|
||||
<AuthFooterLink>
|
||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||
</AuthFooterLink>
|
||||
)}
|
||||
<AuthFooterLink>
|
||||
<Link to={'/auth/send_reset_password'}>
|
||||
<T id={'forget_my_password'} />
|
||||
|
||||
@@ -10,7 +10,7 @@ import AuthInsider from '@/containers/Authentication/AuthInsider';
|
||||
import { useAuthLogin, useAuthRegister } from '@/hooks/query/authentication';
|
||||
|
||||
import RegisterForm from './RegisterForm';
|
||||
import { RegisterSchema, transformRegisterErrorsToForm } from './utils';
|
||||
import { RegisterSchema, transformRegisterErrorsToForm, transformRegisterToastMessages } from './utils';
|
||||
import {
|
||||
AuthFooterLinks,
|
||||
AuthFooterLink,
|
||||
@@ -57,7 +57,11 @@ export default function RegisterUserForm() {
|
||||
},
|
||||
}) => {
|
||||
const formErrors = transformRegisterErrorsToForm(errors);
|
||||
const toastMessages = transformRegisterToastMessages(errors);
|
||||
|
||||
toastMessages.forEach((toastMessage) => {
|
||||
AppToaster.show(toastMessage);
|
||||
});
|
||||
setErrors(formErrors);
|
||||
setSubmitting(false);
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from './_components';
|
||||
import ResetPasswordForm from './ResetPasswordForm';
|
||||
import { ResetPasswordSchema } from './utils';
|
||||
import { useAuthMetaBoot } from './AuthMetaBoot';
|
||||
|
||||
const initialValues = {
|
||||
password: '',
|
||||
@@ -79,12 +80,15 @@ export default function ResetPassword() {
|
||||
}
|
||||
|
||||
function ResetPasswordFooterLinks() {
|
||||
const { signupDisabled } = useAuthMetaBoot();
|
||||
|
||||
return (
|
||||
<AuthFooterLinks>
|
||||
<AuthFooterLink>
|
||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||
</AuthFooterLink>
|
||||
|
||||
{!signupDisabled && (
|
||||
<AuthFooterLink>
|
||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||
</AuthFooterLink>
|
||||
)}
|
||||
<AuthFooterLink>
|
||||
Return to <Link to={'/auth/login'}>Sign In</Link>
|
||||
</AuthFooterLink>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
transformSendResetPassErrorsToToasts,
|
||||
} from './utils';
|
||||
import AuthInsider from '@/containers/Authentication/AuthInsider';
|
||||
import { useAuthMetaBoot } from './AuthMetaBoot';
|
||||
|
||||
const initialValues = {
|
||||
crediential: '',
|
||||
@@ -27,7 +28,7 @@ const initialValues = {
|
||||
/**
|
||||
* Send reset password page.
|
||||
*/
|
||||
export default function SendResetPassword({ requestSendResetPassword }) {
|
||||
export default function SendResetPassword() {
|
||||
const history = useHistory();
|
||||
const { mutateAsync: sendResetPasswordMutate } = useAuthSendResetPassword();
|
||||
|
||||
@@ -75,12 +76,15 @@ export default function SendResetPassword({ requestSendResetPassword }) {
|
||||
}
|
||||
|
||||
function SendResetPasswordFooterLinks() {
|
||||
const { signupDisabled } = useAuthMetaBoot();
|
||||
|
||||
return (
|
||||
<AuthFooterLinks>
|
||||
<AuthFooterLink>
|
||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||
</AuthFooterLink>
|
||||
|
||||
{!signupDisabled && (
|
||||
<AuthFooterLink>
|
||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||
</AuthFooterLink>
|
||||
)}
|
||||
<AuthFooterLink>
|
||||
Return to <Link to={'/auth/login'}>Sign In</Link>
|
||||
</AuthFooterLink>
|
||||
|
||||
@@ -94,3 +94,21 @@ export const transformRegisterErrorsToForm = (errors) => {
|
||||
}
|
||||
return formErrors;
|
||||
};
|
||||
|
||||
export const transformRegisterToastMessages = (errors) => {
|
||||
const toastErrors = [];
|
||||
|
||||
if (errors.some((e) => e.type === 'SIGNUP_RESTRICTED_NOT_ALLOWED')) {
|
||||
toastErrors.push({
|
||||
message:
|
||||
'The sign-up is restricted, the given email address is not allowed to sign-up.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
} else if (errors.find((e) => e.type === 'SIGNUP_RESTRICTED')) {
|
||||
toastErrors.push({
|
||||
message: 'Sign-up is disabled, and no new accounts can be created.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
return toastErrors;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { useMutation } from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
import { setCookie } from '../../utils';
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
import t from './types';
|
||||
|
||||
/**
|
||||
* Saves the response data to cookies.
|
||||
@@ -70,3 +72,21 @@ export const useAuthResetPassword = (props) => {
|
||||
props,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the authentication page metadata.
|
||||
*/
|
||||
export const useAuthMetadata = (props) => {
|
||||
return useRequestQuery(
|
||||
[t.AUTH_METADATA_PAGE,],
|
||||
{
|
||||
method: 'get',
|
||||
url: `auth/meta`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// @ts-nocheck
|
||||
const Authentication = {
|
||||
AUTH_METADATA_PAGE: 'AUTH_META_PAGE'
|
||||
}
|
||||
|
||||
const ACCOUNTS = {
|
||||
ACCOUNT: 'ACCOUNT',
|
||||
ACCOUNT_TRANSACTION: 'ACCOUNT_TRANSACTION',
|
||||
@@ -217,6 +221,7 @@ const DASHBOARD = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...Authentication,
|
||||
...ACCOUNTS,
|
||||
...BILLS,
|
||||
...VENDORS,
|
||||
|
||||
Reference in New Issue
Block a user