Add service account support for organization endpoints and update docs images

This commit is contained in:
Tuan Dang
2023-04-10 17:30:51 +03:00
parent afa7b35d50
commit 5032450b1c
34 changed files with 189 additions and 57 deletions
+2 -2
View File
@@ -22,8 +22,8 @@ const validateMembershipOrg = async ({
}: {
userId: Types.ObjectId;
organizationId: Types.ObjectId;
acceptedRoles: string[];
acceptedStatuses: string[];
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
const membershipOrg = await MembershipOrg.findOne({
user: userId,
+50 -9
View File
@@ -15,7 +15,8 @@ import {
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
AUTH_MODE_API_KEY,
OWNER
} from '../variables';
import {
getStripeSecretKey,
@@ -24,8 +25,15 @@ import {
getStripeProductStarter
} from '../config';
import {
UnauthorizedRequestError
UnauthorizedRequestError,
OrganizationNotFoundError
} from '../utils/errors';
import {
validateUserClientForOrganization
} from '../helpers/user';
import {
validateServiceAccountClientForOrganization
} from '../helpers/serviceAccount';
/**
* Validate accepted clients for organization with id [organizationId]
@@ -35,30 +43,62 @@ import {
*/
const validateClientForOrganization = async ({
authData,
organizationId
organizationId,
acceptedRoles,
acceptedStatuses
}: {
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
},
organizationId: string;
organizationId: Types.ObjectId;
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
// TODO
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: 'Failed to find organization'
});
}
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
// TODO
const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload,
organization,
acceptedRoles,
acceptedStatuses
});
return ({ organization, membershipOrg });
}
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
// TODO
await validateServiceAccountClientForOrganization({
serviceAccount: authData.authPayload,
organization
});
return ({ organization });
}
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
// TODO
throw UnauthorizedRequestError({
message: 'Failed service token authorization for organization resource'
});
}
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
// TODO
const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload,
organization,
acceptedRoles,
acceptedStatuses
});
return ({ organization, membershipOrg });
}
throw UnauthorizedRequestError({
@@ -228,6 +268,7 @@ const updateSubscriptionOrgQuantity = async ({
};
export {
validateClientForOrganization,
createOrganization,
initSubscriptionOrg,
updateSubscriptionOrgQuantity
+23 -9
View File
@@ -8,6 +8,7 @@ import {
ServiceTokenData,
IServiceTokenData,
ISecret,
IOrganization,
ServiceAccountWorkspacePermission
} from '../models';
import {
@@ -109,9 +110,6 @@ const validateClientForServiceAccount = async ({
environment?: string;
requiredPermissions?: string[];
}) => {
// TODO: add service account API support for workspace-level endpoints that are not
// tied to any specific environment
if (environment) {
const permission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount,
@@ -123,7 +121,6 @@ const validateClientForServiceAccount = async ({
message: 'Failed service account authorization for the given workspace environment'
});
// TODO: refactor
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
@@ -180,7 +177,6 @@ const validateClientForServiceAccount = async ({
});
requiredPermissions?.forEach((requiredPermission: string) => {
// TODO: refactor
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
@@ -202,9 +198,6 @@ const validateClientForServiceAccount = async ({
});
});
});
// TODO
return [];
}
/**
@@ -231,9 +224,30 @@ const validateServiceAccountClientForServiceAccount = ({
}
}
/**
* Validate that service account (client) can access organization [organization]
* @param {Object} obj
* @param {User} obj.user - service account client
* @param {Organization} obj.organization - organization to validate against
*/
const validateServiceAccountClientForOrganization = async ({
serviceAccount,
organization
}: {
serviceAccount: IServiceAccount;
organization: IOrganization;
}) => {
if (!serviceAccount.organization.equals(organization._id)) {
throw UnauthorizedRequestError({
message: 'Failed service account authorization for the given organization'
});
}
}
export {
validateClientForServiceAccount,
validateServiceAccountClientForWorkspace,
validateServiceAccountClientForSecrets,
validateServiceAccountClientForServiceAccount
validateServiceAccountClientForServiceAccount,
validateServiceAccountClientForOrganization
}
+32 -2
View File
@@ -5,7 +5,9 @@ import {
ISecret,
IServiceAccount,
User,
Membership
Membership,
IOrganization,
Organization,
} from '../models';
import { sendMail } from './nodemailer';
import { validateMembership } from './membership';
@@ -288,11 +290,39 @@ const validateUserClientForServiceAccount = async ({
}
}
/**
* Validate that user (client) can access organization [organization]
* @param {Object} obj
* @param {User} obj.user - user client
* @param {Organization} obj.organization - organization to validate against
*/
const validateUserClientForOrganization = async ({
user,
organization,
acceptedRoles,
acceptedStatuses
}: {
user: IUser;
organization: IOrganization;
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
const membershipOrg = await validateMembershipOrg({
userId: user._id,
organizationId: organization._id,
acceptedRoles,
acceptedStatuses
});
return membershipOrg;
}
export {
setupAccount,
completeAccount,
checkUserDevice,
validateUserClientForWorkspace,
validateUserClientForSecrets,
validateUserClientForServiceAccount
validateUserClientForServiceAccount,
validateUserClientForOrganization
};
+2 -1
View File
@@ -44,6 +44,7 @@ const requireAuth = ({
acceptedAuthModes: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
// validate auth token against accepted auth modes [acceptedAuthModes]
// and return token type [authTokenType] and value [authTokenValue]
const { authMode, authTokenValue } = validateAuthMode({
@@ -87,7 +88,7 @@ const requireAuth = ({
req.authData = {
authMode,
authPayload
authPayload // User, ServiceAccount, ServiceTokenData
}
return next();
@@ -20,8 +20,8 @@ const requireMembershipOrgAuth = ({
acceptedStatuses,
location = 'params'
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
@@ -3,6 +3,7 @@ import { Types } from 'mongoose';
import { IOrganization, MembershipOrg } from '../models';
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
import { validateMembershipOrg } from '../helpers/membershipOrg';
import { validateClientForOrganization } from '../helpers/organization';
type req = 'params' | 'body' | 'query';
@@ -16,20 +17,31 @@ type req = 'params' | 'body' | 'query';
const requireOrganizationAuth = ({
acceptedRoles,
acceptedStatuses,
location = 'params'
locationOrganizationId = 'params'
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
location?: req;
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
locationOrganizationId?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { organizationId } = req[location];
req.membershipOrg = await validateMembershipOrg({
userId: req.user._id,
const { organizationId } = req[locationOrganizationId];
// TODO: incorporate [acceptedRoles] and [acceptedStatuses]
const { organization, membershipOrg } = await validateClientForOrganization({
authData: req.authData,
organizationId: new Types.ObjectId(organizationId),
acceptedRoles,
acceptedStatuses
});
if (organization) {
req.organization = organization;
}
if (membershipOrg) {
req.membershipOrg = membershipOrg;
}
return next();
};
@@ -14,8 +14,8 @@ const requireServiceAccountWorkspacePermissionAuth = ({
acceptedStatuses,
location = 'params'
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
+2 -2
View File
@@ -1,7 +1,7 @@
import { Schema, model, Types } from 'mongoose';
import { Schema, model, Types, Document } from 'mongoose';
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from '../variables';
export interface IMembershipOrg {
export interface IMembershipOrg extends Document {
_id: Types.ObjectId;
user: Types.ObjectId;
inviteEmail: string;
+1 -1
View File
@@ -53,7 +53,7 @@ router.post(
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
location: 'body'
locationOrganizationId: 'body'
}),
serviceAccountsController.createServiceAccount
);