Skip to content

Commit

Permalink
feat/get-tenant-of-authenticated-user
Browse files Browse the repository at this point in the history
Tasks done
---

- Created api endpoint to fetch authenticated user details and its tenant.
- Added cors middleware to allow resources to whitelisted domains.
  • Loading branch information
er-santosh committed Mar 27, 2024
1 parent 16323e9 commit be546b2
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 1 deletion.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,7 @@ OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_REDIRECT_URI=
OIDC_SCOPE=
OIDC_JWK_URI=
OIDC_JWK_URI=

#CORS
CORS_ALLOWED_DOMAINS=http://localhost:4000,http://localhost:5000
3 changes: 3 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ services:
- OIDC_SCOPE=${OIDC_SCOPE}
- OIDC_JWK_URI=${OIDC_JWK_URI}

#CORS
- CORS_ALLOWED_DOMAINS=${CORS_ALLOWED_DOMAINS}

database_migration:
container_name: bigcapital-database-migration
build:
Expand Down
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"cors": "^2.8.5",
"country-codes-list": "^1.6.8",
"cpy": "^8.1.2",
"cpy-cli": "^3.1.1",
Expand Down
29 changes: 29 additions & 0 deletions packages/server/src/api/controllers/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ServiceError, ServiceErrors } from '@/exceptions';
import { DATATYPES_LENGTH } from '@/data/DataTypes';
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
import AuthenticationApplication from '@/services/Authentication/AuthApplication';
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
import JWTAuth from '@/api/middleware/jwtAuth';

@Service()
export default class AuthenticationController extends BaseController {
Expand Down Expand Up @@ -50,6 +52,12 @@ export default class AuthenticationController extends BaseController {
this.handlerErrors
);
router.get('/meta', asyncMiddleware(this.getAuthMeta.bind(this)));

router.use(JWTAuth);
router.use(AttachCurrentTenantUser);

router.get('/me', asyncMiddleware(this.getAuthMe.bind(this)));

return router;
}

Expand Down Expand Up @@ -226,6 +234,27 @@ export default class AuthenticationController extends BaseController {
}
}

/**
* Retrieves the authentication user and its tenant
* @param {Request} req
* @param {Response} res
* @param {Function} next
* @returns {Response|void}
*/
private async getAuthMe(req: Request, res: Response, next: Function) {
try {
const { user } = req;

const tenantId = user.tenantId;

const tenant = await this.authApplication.getAuthTenant(tenantId);

return res.status(200).send({ user, tenant });
} catch (error) {
next(error);
}
}

/**
* Handles the service errors.
*/
Expand Down
18 changes: 18 additions & 0 deletions packages/server/src/api/middleware/CorsMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import cors from 'cors';
import config from '@/config';

const corsMiddleware = cors({
origin: function (origin, callback) {
const allowedDomains = config.cors.whitelistedDomains;

const requestOrigin = origin?.endsWith('/') ? origin?.slice(0, -1) : origin;

if (allowedDomains.indexOf(requestOrigin) !== -1 || !requestOrigin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
});

export default corsMiddleware;
2 changes: 2 additions & 0 deletions packages/server/src/api/middleware/jwtAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const authMiddleware = (req: Request, res: Response, next: NextFunction) => {

const systemUser = await systemUserRepository.findOneByEmail(email);

if (!systemUser) throw new Error(`User with email: ${email} not found`);

const payload = {
id: systemUser.id,
oidc_access_token: token,
Expand Down
5 changes: 5 additions & 0 deletions packages/server/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,9 @@ module.exports = {
oidcLogin: {
disabled: parseBoolean<boolean>(process.env.OIDC_LOGIN_DISABLED, false),
},
cors: {
whitelistedDomains: castCommaListEnvVarToArray(
process.env.CORS_ALLOWED_DOMAINS
),
},
};
4 changes: 4 additions & 0 deletions packages/server/src/loaders/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import config from '@/config';
import path from 'path';
import ObjectionErrorHandlerMiddleware from '@/api/middleware/ObjectionErrorHandlerMiddleware';
import corsMiddleware from '@/api/middleware/CorsMiddleware';

export default ({ app }) => {
// Express configuration.
Expand All @@ -30,6 +31,9 @@ export default ({ app }) => {
// Helmet helps you secure your Express apps by setting various HTTP headers.
app.use(helmet());

// Cors middleware.
app.use(corsMiddleware);

// Allow to full error stack traces and internal details
app.use(errorHandler());

Expand Down
14 changes: 14 additions & 0 deletions packages/server/src/services/Authentication/AuthApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {
ISystemUser,
IPasswordReset,
IAuthGetMetaPOJO,
ITenant,
} from '@/interfaces';
import { AuthSigninService } from './AuthSignin';
import { AuthSignupService } from './AuthSignup';
import { AuthSendResetPassword } from './AuthSendResetPassword';
import { GetAuthMeta } from './GetAuthMeta';
import { GetAuthMe } from './GetAuthMe';

@Service()
export default class AuthenticationApplication {
Expand All @@ -24,6 +26,9 @@ export default class AuthenticationApplication {
@Inject()
private authGetMeta: GetAuthMeta;

@Inject()
private authGetMe: GetAuthMe;

/**
* Signin and generates JWT token.
* @throws {ServiceError}
Expand Down Expand Up @@ -70,4 +75,13 @@ export default class AuthenticationApplication {
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
return this.authGetMeta.getAuthMeta();
}

/**
* Retrieves the authenticated tenant
* @param {number} tenantId
* @returns {Promise<ITenant>}
*/
public async getAuthTenant(tenantId: number): Promise<ITenant> {
return this.authGetMe.getAuthTenant(tenantId);
}
}
20 changes: 20 additions & 0 deletions packages/server/src/services/Authentication/GetAuthMe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Inject, Service } from 'typedi';
import { ITenant } from '@/interfaces';
import { TenantService } from '@/services/Tenancy/TenantService';

@Service()
export class GetAuthMe {
@Inject()
private tenantService: TenantService;

/**
* Retrieves the authenticated tenant.
* @param {number} tenantId
* @returns {Promise<ITenant>}
*/
public async getAuthTenant(tenantId: number): Promise<ITenant> {
const tenant = await this.tenantService.getTenantById(tenantId);

return tenant;
}
}
21 changes: 21 additions & 0 deletions packages/server/src/services/Tenancy/TenantService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Service } from 'typedi';
import { Tenant } from '@/system/models';
import ModelEntityNotFound from '@/exceptions/ModelEntityNotFound';

@Service()
export class TenantService {
/**
* Retrieves tenant by id.
* @param {number} tenantId
* @returns {Promise<any>}
*/
public async getTenantById(tenantId: number): Promise<any> {
const tenant = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');

if (!tenant) throw new ModelEntityNotFound(tenantId);

return tenant;
}
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit be546b2

Please sign in to comment.