Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/KAYA-28-Compose-a-middleware-for-user-application-checking #29

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions auth-server/src/middlewares/require-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { RequestHandler } from 'express';
import httpErrors from 'http-errors';
import jsonwebtoken from 'jsonwebtoken';
import prisma from '../lib/prisma.js';
import type { Users } from '@prisma/client';
import type { Locals as AccessTokenEnforcerLocals } from './require-access-token.js';

export type Locals<S extends boolean> =
& AccessTokenEnforcerLocals
& (S extends true ? Record<'user', Users> : Record<'user', Users | null>);
export type UserEnforcer<S extends boolean> = RequestHandler<unknown, unknown, unknown, unknown, Locals<S>>;

export default function<S extends boolean> (strict: S = true as S): UserEnforcer<S> {
return async (_request, response, next) => {
try {
// Fetch the user using the access token from response locals
const user = await prisma.users.findUnique({
where: {
email: response.locals.accessTokenData.email
}
});

if (strict && !user) {
throw httpErrors.Unauthorized('User not found');
}
response.locals.user = user;

next();
} catch (error) {
if (error instanceof jsonwebtoken.TokenExpiredError) {
response.clearCookie('access_token');
return next(httpErrors.Unauthorized('Access token expired'));
}
next(error);
}
};
}
29 changes: 29 additions & 0 deletions auth-server/src/middlewares/require-userApplication-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RequestHandler } from 'express';
import httpErrors from 'http-errors';
import prisma from '../lib/prisma.js';
import type { Locals as ApplicationEnforcerLocals } from './require-application.js';
import type { Locals as UserEnforcerLocals } from './require-user.js';

export type Locals = ApplicationEnforcerLocals & UserEnforcerLocals<true>;
export type UserApplicationLinkEnforcer = RequestHandler<unknown, unknown, unknown, unknown, Locals>;

export default function (): UserApplicationLinkEnforcer {
return async (_request, response, next) => {
try {
const userApplicationMap = await prisma.userApplicationMap.findUnique({
where: {
userId_applicationId: {
userId: response.locals.user.id,
applicationId: response.locals.application.id
}
}
});
if (!userApplicationMap) {
throw httpErrors.Unauthorized(`User is not authorized to use ${response.locals.application.name}`);
}
next();
} catch (error) {
next(error);
}
};
}
7 changes: 6 additions & 1 deletion auth-server/src/routers/auth-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import requireAccessToken from "../middlewares/require-access-token.js";
import { generateAccessToken } from "../lib/token.js";
import { Http } from "../config/environment.js";
import requireApplication from "../middlewares/require-application.js";
import requireUser from "../middlewares/require-user.js";
import requireUserApplicationLink from "../middlewares/require-userApplication-link.js";

const authRouter = Router();

Expand Down Expand Up @@ -41,7 +43,7 @@ authRouter.post(
return res
.cookie("access_token", token, { httpOnly: true, secure: true, sameSite: "none" })
.status(httpStatus.OK)
.json({ token }); // TODO: Remove access token from response
.end();
} catch (error) {
next(error);
}
Expand All @@ -63,8 +65,11 @@ authRouter.delete(

authRouter.get(
"/auth/verify",
requireHeaders("X-Application"),
requireApplication(),
requireAccessToken(),
requireUser(),
requireUserApplicationLink(),
async (_req, res, next) => {
try {
if (Http.responseVerifyTokenCacheEnable) {
Expand Down
29 changes: 19 additions & 10 deletions auth-server/src/routers/user-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import validator from "validator";
import requireBody from "../middlewares/require-body.js";
import prisma from "../lib/prisma.js";
import { Bcrypt } from "../config/environment.js";
import requireUserApplicationLink from "../middlewares/require-userApplication-link.js";
import requireApplication from "../middlewares/require-application.js";
import requireUser from "../middlewares/require-user.js";
import requireAccessToken from "../middlewares/require-access-token.js";

const userRouter = Router();

Expand All @@ -19,8 +23,12 @@ interface CreateUserBody {

type RequiredKeys = "firstName" | "lastName" | "email" | "password";

userRouter.post(
userRouter.post(
'/users/register',
requireApplication(),
requireAccessToken(),
requireUser(),
requireUserApplicationLink(),
requireBody<CreateUserBody, RequiredKeys>("firstName", "lastName", "email", "password"),
async (req, res, next) => {
try {
Expand All @@ -39,15 +47,16 @@ userRouter.post(
}
const passwordHash = await hash(password, Bcrypt.saltRounds);

const newUser = await prisma.users.create({
data: {
firstName,
middleName,
lastName,
email,
password: passwordHash,
}
})
const newUser = await prisma.users
.create({
data: {
firstName,
middleName,
lastName,
email,
password: passwordHash,
}
})
.then(({ password, ...rest }) => rest);

res.status(httpStatus.CREATED).json(newUser);
Expand Down
10 changes: 5 additions & 5 deletions hr-resource/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ LOGGER_DIRECTORY=./logs
LOGGER_DISABLE_FILE_OUTPUT=true
LOGGER_ENABLE_STACK_TRACE=true
LOGGER_OUTPUT_FILENAME=app.log
SEED_DEFAULT_ORGANIZATION_NAME=KayaHR
SEED_DEFAULT_ORGANIZATION_SUMMARY="We are KayaHR"
SEED_DEFAULT_ORGANIZATION_NAME="Kaya"
SEED_DEFAULT_ORGANIZATION_SUMMARY="We are Kaya"
SEED_DEFAULT_ORGANIZATION_WEBSITE_URL="https://kaya-hr.com"
SEED_DEFAULT_ROLE_CODE=SUPER
SEED_DEFAULT_ROLE_DESCRIPTION="Full access across the system"
SEED_DEFAULT_ROLE_HOURLY_WAGE=0
SEED_DEFAULT_ROLE_TITLE="Super Admin"
SEED_DEFAULT_USER_CITY=Metropolis
SEED_DEFAULT_USER_COUNTRY=Wonderland
SEED_DEFAULT_USER_DATE_JOINED="January 01 2022"
SEED_DEFAULT_USER_DATE_OF_BIRTH="January 01 1990"
SEED_DEFAULT_USER_DATE_JOINED="2022-01-01T05:00:00.000Z"
SEED_DEFAULT_USER_DATE_OF_BIRTH="1990-01-01T05:00:00.000Z"
SEED_DEFAULT_USER_EMAIL=admin@kaya-hr.com
SEED_DEFAULT_USER_FIRST_NAME=Admin
SEED_DEFAULT_USER_LAST_NAME=atKaya
SEED_DEFAULT_USER_PASSWORD=\$RandomPass567
SEED_DEFAULT_USER_PHONE=123-456-7890
SEED_DEFAULT_USER_PHONE="+1 (123) 456-7890"
SEED_DEFAULT_USER_PINCODE="N2N 2N2"
SEED_DEFAULT_USER_PROVINCE=Central
SEED_DEFAULT_USER_STREET_NAME="Main Street"
3 changes: 2 additions & 1 deletion hr-resource/src/lib/apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ export const apolloServerContextFn: ExpressMiddlewareOptions<ApolloServerContext
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (value === undefined) continue;
if (key.match(/^content-length$/i)) continue;
headers.append(key, Array.isArray(value) ? value.join(',') : value);
}

const verificationResponse = await verifyIdentity({ headers });
if (!verificationResponse.ok) {
const errorBody = await verificationResponse.json() as Error;
Expand Down