Skip to content

Commit

Permalink
Begin service account middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
dangtony98 committed Mar 30, 2023
1 parent 54e099f commit 9c18adf
Show file tree
Hide file tree
Showing 31 changed files with 278 additions and 145 deletions.
4 changes: 2 additions & 2 deletions backend/src/controllers/v2/serviceAccountsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
const secretId = Buffer.from(serviceAccount._id.toString(), 'hex').toString('base64');

return res.status(200).send({
serviceAccountAccessKey: `SA.${secretId}.${secret}`,
serviceAccountAccessKey: `sa.${secretId}.${secret}`,
serviceAccount: serviceAccountObj
});
}
Expand Down Expand Up @@ -211,7 +211,7 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re

const existingPermission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspaceId: new Types.ObjectId(workspaceId),
workspace: new Types.ObjectId(workspaceId),
environment
});

Expand Down
5 changes: 5 additions & 0 deletions backend/src/controllers/v2/workspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,8 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
});
};

export const getAak = (req: Request, res: Response) => {
return res.status(200).send({
message: 'getAak'
});
}
4 changes: 2 additions & 2 deletions backend/src/ee/middleware/requireSecretSnapshotAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const requireSecretSnapshotAuth = ({
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: secretSnapshot.workspace.toString(),
userId: req.user._id,
workspaceId: secretSnapshot.workspace,
acceptedRoles
});

Expand Down
12 changes: 8 additions & 4 deletions backend/src/ee/routes/v1/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ router.get(
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'params'
}),
param('workspaceId').exists().trim(),
query('offset').exists().isInt(),
Expand All @@ -30,7 +31,8 @@ router.get(
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'params'
}),
param('workspaceId').exists().trim(),
validateRequest,
Expand All @@ -43,7 +45,8 @@ router.post(
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'params'
}),
param('workspaceId').exists().trim(),
body('version').exists().isInt(),
Expand All @@ -57,7 +60,8 @@ router.get(
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER]
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'params'
}),
param('workspaceId').exists().trim(),
query('offset').exists().isInt(),
Expand Down
37 changes: 37 additions & 0 deletions backend/src/helpers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {
IUser,
User,
ServiceTokenData,
ServiceAccount,
APIKeyData
} from '../models';
import {
AccountNotFoundError,
ServiceTokenDataNotFoundError,
ServiceAccountNotFoundError,
APIKeyDataNotFoundError,
UnauthorizedRequestError,
BadRequestError
Expand Down Expand Up @@ -63,9 +65,13 @@ const validateAuthMode = ({
case 'st':
authTokenType = 'serviceToken';
break;
case 'sa':
authTokenType = 'serviceAccount';
break;
default:
authTokenType = 'jwt';
}

authTokenValue = tokenValue;
}

Expand Down Expand Up @@ -164,6 +170,36 @@ const getAuthSTDPayload = async ({
}

/**
* Return service account access key payload
* @param {Object} obj
* @param {String} obj.authTokenValue - service account access token value
* @returns {ServiceAccount} serviceAccount
*/
const getAuthSAAKPayload = async ({
authTokenValue
}: {
authTokenValue: string;
}) => {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);

const serviceAccount = await ServiceAccount.findById(
Buffer.from(TOKEN_IDENTIFIER, 'base64').toString('hex')
).select('+secretHash');

if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
}

const result = await bcrypt.compare(TOKEN_SECRET, serviceAccount.secretHash);
if (!result) throw UnauthorizedRequestError({
message: 'Failed to authenticate service account access key'
});

return serviceAccount;
}

/**
* TODO: deprecate API keys
* Return API key data payload corresponding to API key [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - API key value
Expand Down Expand Up @@ -300,6 +336,7 @@ export {
validateAuthMode,
getAuthUserPayload,
getAuthSTDPayload,
getAuthSAAKPayload,
getAuthAPIKeyPayload,
createToken,
issueAuthTokens,
Expand Down
13 changes: 8 additions & 5 deletions backend/src/helpers/membership.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Membership, Key } from '../models';
import {
MembershipNotFoundError,
Expand All @@ -18,9 +19,9 @@ const validateMembership = async ({
workspaceId,
acceptedRoles,
}: {
userId: string;
workspaceId: string;
acceptedRoles: string[];
userId: Types.ObjectId;
workspaceId: Types.ObjectId;
acceptedRoles?: string[];
}) => {

const membership = await Membership.findOne({
Expand All @@ -32,8 +33,10 @@ const validateMembership = async ({
throw MembershipNotFoundError({ message: 'Failed to find workspace membership' });
}

if (!acceptedRoles.includes(membership.role)) {
throw BadRequestError({ message: 'Failed to validate workspace membership role' });
if (acceptedRoles) {
if (!acceptedRoles.includes(membership.role)) {
throw BadRequestError({ message: 'Failed to validate workspace membership role' });
}
}

return membership;
Expand Down
51 changes: 50 additions & 1 deletion backend/src/helpers/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
Workspace,
Bot,
Expand All @@ -7,6 +8,50 @@ import {
Secret
} from '../models';
import { createBot } from '../helpers/bot';
import { validateMembership } from '../helpers/membership';

/**
* Validate accepted clients by id including [userId], [serviceAccountId],
* and [serviceTokenDataId] for workspace with id [workspaceId] based
* on any known permissions.
* @param {Object} obj
* @param {Types.ObjectId} obj.userId - id of user
*/
const validateClientForWorkspace = async ({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
environment
}: {
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId: Types.ObjectId;
environment?: string;
}) => {

let membership;
if (userId) {
membership = await validateMembership({
userId,
workspaceId
});

}

if (serviceAccountId) {
// TODO
}

if (serviceTokenDataId) {
// TODO
}

return ({
membership
});
}

/**
* Create a workspace with name [name] in organization with id [organizationId]
Expand Down Expand Up @@ -71,4 +116,8 @@ const deleteWorkspace = async ({ id }: { id: string }) => {
}
};

export { createWorkspace, deleteWorkspace };
export {
validateClientForWorkspace,
createWorkspace,
deleteWorkspace
};
14 changes: 12 additions & 2 deletions backend/src/middleware/requireAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
validateAuthMode,
getAuthUserPayload,
getAuthSTDPayload,
getAuthAPIKeyPayload
getAuthAPIKeyPayload,
getAuthSAAKPayload
} from '../helpers/auth';
import {
UnauthorizedRequestError
Expand Down Expand Up @@ -41,14 +42,22 @@ const requireAuth = ({
acceptedAuthModes
});

req.authTokenType = authTokenType;

// attach auth payloads
let serviceTokenData: any;
switch (authTokenType) {
case 'serviceAccount':
req.serviceAccount = await getAuthSAAKPayload({
authTokenValue
});
break;
case 'serviceToken':
serviceTokenData = await getAuthSTDPayload({
authTokenValue
});

// TODO: bring this into a separate collection
requiredServiceTokenPermissions.forEach((requiredServiceTokenPermission) => {
if (!serviceTokenData.permissions.includes(requiredServiceTokenPermission)) {
return next(UnauthorizedRequestError({ message: 'Failed to authorize service token for endpoint' }));
Expand All @@ -60,6 +69,7 @@ const requireAuth = ({

break;
case 'apiKey':
// TODO: deprecate API key
req.user = await getAuthAPIKeyPayload({
authTokenValue
});
Expand All @@ -70,7 +80,7 @@ const requireAuth = ({
});
break;
}

return next();
}
}
Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/requireBotAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const requireBotAuth = ({
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: bot.workspace.toString(),
userId: req.user._id,
workspaceId: bot.workspace,
acceptedRoles
});

Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/requireIntegrationAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const requireIntegrationAuth = ({
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: integration.workspace.toString(),
userId: req.user._id,
workspaceId: integration.workspace,
acceptedRoles
});

Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/requireIntegrationAuthorizationAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const requireIntegrationAuthorizationAuth = ({
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: integrationAuth.workspace._id.toString(),
userId: req.user._id,
workspaceId: integrationAuth.workspace._id,
acceptedRoles
});

Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/requireMembershipAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const requireMembershipAuth = ({
if (!userMembership) throw new Error('Failed to validate own membership')

const targetMembership = await validateMembership({
userId: req.user._id.toString(),
workspaceId: membership.workspace.toString(),
userId: req.user._id,
workspaceId: membership.workspace,
acceptedRoles
});

Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/requireSecretAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const requireSecretAuth = ({
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: secret.workspace.toString(),
userId: req.user._id,
workspaceId: secret.workspace,
acceptedRoles
});

Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/requireServiceTokenDataAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const requireServiceTokenDataAuth = ({
if (req.user) {
// case: jwt auth
await validateMembership({
userId: req.user._id.toString(),
workspaceId: serviceTokenData.workspace.toString(),
userId: req.user._id,
workspaceId: serviceTokenData.workspace,
acceptedRoles
});
}
Expand Down
Loading

0 comments on commit 9c18adf

Please sign in to comment.