mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Checkpoint service accounts
This commit is contained in:
@@ -7,6 +7,7 @@ import * as serviceTokenDataController from './serviceTokenDataController';
|
|||||||
import * as apiKeyDataController from './apiKeyDataController';
|
import * as apiKeyDataController from './apiKeyDataController';
|
||||||
import * as secretController from './secretController';
|
import * as secretController from './secretController';
|
||||||
import * as secretsController from './secretsController';
|
import * as secretsController from './secretsController';
|
||||||
|
import * as serviceAccountsController from './serviceAccountsController';
|
||||||
import * as environmentController from './environmentController';
|
import * as environmentController from './environmentController';
|
||||||
import * as tagController from './tagController';
|
import * as tagController from './tagController';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export {
|
|||||||
apiKeyDataController,
|
apiKeyDataController,
|
||||||
secretController,
|
secretController,
|
||||||
secretsController,
|
secretsController,
|
||||||
|
serviceAccountsController,
|
||||||
environmentController,
|
environmentController,
|
||||||
tagController
|
tagController
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
Membership,
|
Membership,
|
||||||
Workspace
|
Workspace,
|
||||||
|
ServiceAccount
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
|
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
|
||||||
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
||||||
@@ -260,37 +262,44 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let workspaces;
|
const { organizationId } = req.params;
|
||||||
try {
|
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
const workspacesSet = new Set(
|
const workspacesSet = new Set(
|
||||||
(
|
(
|
||||||
await Workspace.find(
|
await Workspace.find(
|
||||||
{
|
{
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
},
|
},
|
||||||
'_id'
|
'_id'
|
||||||
)
|
)
|
||||||
).map((w) => w._id.toString())
|
).map((w) => w._id.toString())
|
||||||
);
|
);
|
||||||
|
|
||||||
workspaces = (
|
const workspaces = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
user: req.user._id
|
user: req.user._id
|
||||||
}).populate('workspace')
|
}).populate('workspace')
|
||||||
)
|
)
|
||||||
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||||
.map((m) => m.workspace);
|
.map((m) => m.workspace);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get organization workspaces'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspaces
|
workspaces
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service accounts for organization with id [organizationId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params;
|
||||||
|
const serviceAccounts = await ServiceAccount.find({
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccounts
|
||||||
|
});
|
||||||
|
}
|
||||||
178
backend/src/controllers/v2/serviceAccountsController.ts
Normal file
178
backend/src/controllers/v2/serviceAccountsController.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceAccountKey,
|
||||||
|
ServiceAccountPermission
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
CreateServiceAccountDto
|
||||||
|
} from '../../interfaces/serviceAccounts/dto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new service account under organization with id [organizationId]
|
||||||
|
* that has access to workspaces [workspaces]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createServiceAccount = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
organizationId,
|
||||||
|
name,
|
||||||
|
publicKey,
|
||||||
|
expiresIn,
|
||||||
|
}: CreateServiceAccountDto = req.body;
|
||||||
|
|
||||||
|
let expiresAt;
|
||||||
|
if (expiresIn) {
|
||||||
|
expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccount = await new ServiceAccount({
|
||||||
|
name,
|
||||||
|
organization: new Types.ObjectId(organizationId),
|
||||||
|
user: req.user,
|
||||||
|
publicKey,
|
||||||
|
expiresAt
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
// await Promise.all(
|
||||||
|
// workspaces.map(async ({
|
||||||
|
// workspaceId,
|
||||||
|
// environments,
|
||||||
|
// permissions,
|
||||||
|
// encryptedKey,
|
||||||
|
// nonce
|
||||||
|
// }: {
|
||||||
|
// workspaceId: string;
|
||||||
|
// environments: string[];
|
||||||
|
// permissions: string[];
|
||||||
|
// encryptedKey: string;
|
||||||
|
// nonce: string;
|
||||||
|
// }) => {
|
||||||
|
// const serviceAccountKey = await new ServiceAccountKey({
|
||||||
|
// encryptedKey,
|
||||||
|
// nonce,
|
||||||
|
// sender: req.user._id,
|
||||||
|
// serviceAccount: serviceAccount._id,
|
||||||
|
// workspace: new Types.ObjectId(workspaceId)
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log('serviceAccountKey: ', serviceAccountKey);
|
||||||
|
|
||||||
|
// await Promise.all(
|
||||||
|
// permissions.map(async (name: string) => {
|
||||||
|
// const permission = await new ServiceAccountPermission({
|
||||||
|
// serviceAccount: serviceAccount._id,
|
||||||
|
// name,
|
||||||
|
// workspace: new Types.ObjectId(workspaceId),
|
||||||
|
// environments
|
||||||
|
// }).save();
|
||||||
|
|
||||||
|
// console.log('permission: ', permission);
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Add a service account key to service account with id [serviceAccountId]
|
||||||
|
// * for workspace with id [workspaceId]
|
||||||
|
// * @param req
|
||||||
|
// * @param res
|
||||||
|
// * @returns
|
||||||
|
// */
|
||||||
|
// export const addServiceAccountKey = async (req: Request, res: Response) => {
|
||||||
|
// const {
|
||||||
|
// workspaceId,
|
||||||
|
// encryptedKey,
|
||||||
|
// nonce
|
||||||
|
// } = req.body;
|
||||||
|
|
||||||
|
// const serviceAccountKey = await new ServiceAccountKey({
|
||||||
|
// encryptedKey,
|
||||||
|
// nonce,
|
||||||
|
// sender: req.user._id,
|
||||||
|
// serviceAccount: req.serviceAccount._d,
|
||||||
|
// workspace: new Types.ObjectId(workspaceId)
|
||||||
|
// }).save();
|
||||||
|
|
||||||
|
// return serviceAccountKey;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteServiceAccount = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountId } = req.params;
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);
|
||||||
|
|
||||||
|
await ServiceAccountKey.deleteMany({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addServiceAccountWorkspaceAccess = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountId, workspaceId } = req.params;
|
||||||
|
const {
|
||||||
|
encryptedKey,
|
||||||
|
nonce,
|
||||||
|
permissions // should contain environments
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const serviceAccountKey = await new ServiceAccountKey({
|
||||||
|
encryptedKey,
|
||||||
|
nonce,
|
||||||
|
sender: req.user._id,
|
||||||
|
serviceAccount: req.serviceAccount._id,
|
||||||
|
workspace: new Types.ObjectId('workspaceId')
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceAccountPermissions = await Promise.all(
|
||||||
|
permissions.map
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteServiceAccountWorkspaceAccess = async (req: Request, res: Response) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Add a service account key to service account with id [serviceAccountId]
|
||||||
|
// * for workspace with id [workspaceId]
|
||||||
|
// * @param req
|
||||||
|
// * @param res
|
||||||
|
// * @returns
|
||||||
|
// */
|
||||||
|
// export const addServiceAccountKey = async (req: Request, res: Response) => {
|
||||||
|
// const {
|
||||||
|
// workspaceId,
|
||||||
|
// encryptedKey,
|
||||||
|
// nonce
|
||||||
|
// } = req.body;
|
||||||
|
|
||||||
|
// const serviceAccountKey = await new ServiceAccountKey({
|
||||||
|
// encryptedKey,
|
||||||
|
// nonce,
|
||||||
|
// sender: req.user._id,
|
||||||
|
// serviceAccount: req.serviceAccount._d,
|
||||||
|
// workspace: new Types.ObjectId(workspaceId)
|
||||||
|
// }).save();
|
||||||
|
|
||||||
|
// return serviceAccountKey;
|
||||||
|
// }
|
||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
secret as v2SecretRouter, // begin to phase out
|
secret as v2SecretRouter, // begin to phase out
|
||||||
secrets as v2SecretsRouter,
|
secrets as v2SecretsRouter,
|
||||||
serviceTokenData as v2ServiceTokenDataRouter,
|
serviceTokenData as v2ServiceTokenDataRouter,
|
||||||
|
serviceAccounts as v2ServiceAccountsRouter,
|
||||||
apiKeyData as v2APIKeyDataRouter,
|
apiKeyData as v2APIKeyDataRouter,
|
||||||
environment as v2EnvironmentRouter,
|
environment as v2EnvironmentRouter,
|
||||||
tags as v2TagsRouter,
|
tags as v2TagsRouter,
|
||||||
@@ -148,6 +149,7 @@ const main = async () => {
|
|||||||
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
||||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
app.use('/api/v2/secrets', v2SecretsRouter);
|
||||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
||||||
|
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
||||||
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
||||||
|
|
||||||
// api docs
|
// api docs
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
interface CreateServiceAccountDto {
|
||||||
|
organizationId: string;
|
||||||
|
name: string;
|
||||||
|
publicKey: string;
|
||||||
|
expiresIn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateServiceAccountDto;
|
||||||
5
backend/src/interfaces/serviceAccounts/dto/index.ts
Normal file
5
backend/src/interfaces/serviceAccounts/dto/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import CreateServiceAccountDto from './CreateServiceAccountDto';
|
||||||
|
|
||||||
|
export {
|
||||||
|
CreateServiceAccountDto
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import requireIntegrationAuth from './requireIntegrationAuth';
|
|||||||
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
||||||
import requireServiceTokenAuth from './requireServiceTokenAuth';
|
import requireServiceTokenAuth from './requireServiceTokenAuth';
|
||||||
import requireServiceTokenDataAuth from './requireServiceTokenDataAuth';
|
import requireServiceTokenDataAuth from './requireServiceTokenDataAuth';
|
||||||
|
import requireServiceAccountAuth from './requireServiceAccountAuth';
|
||||||
import requireSecretAuth from './requireSecretAuth';
|
import requireSecretAuth from './requireSecretAuth';
|
||||||
import requireSecretsAuth from './requireSecretsAuth';
|
import requireSecretsAuth from './requireSecretsAuth';
|
||||||
import validateRequest from './validateRequest';
|
import validateRequest from './validateRequest';
|
||||||
@@ -27,6 +28,7 @@ export {
|
|||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
requireServiceTokenDataAuth,
|
requireServiceTokenDataAuth,
|
||||||
|
requireServiceAccountAuth,
|
||||||
requireSecretAuth,
|
requireSecretAuth,
|
||||||
requireSecretsAuth,
|
requireSecretsAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Request, Response, NextFunction } from 'express';
|
|||||||
import { IOrganization, MembershipOrg } from '../models';
|
import { IOrganization, MembershipOrg } from '../models';
|
||||||
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
||||||
|
|
||||||
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request is a member with proper roles for organization
|
* Validate if user on request is a member with proper roles for organization
|
||||||
* on request params.
|
* on request params.
|
||||||
@@ -11,18 +13,22 @@ import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
|||||||
*/
|
*/
|
||||||
const requireOrganizationAuth = ({
|
const requireOrganizationAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses
|
acceptedStatuses,
|
||||||
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses: string[];
|
||||||
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// organization authorization middleware
|
// organization authorization middleware
|
||||||
|
|
||||||
|
const { organizationId } = req[location];
|
||||||
|
|
||||||
// validate organization membership
|
// validate organization membership
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
organization: req.params.organizationId
|
organization: organizationId
|
||||||
}).populate<{ organization: IOrganization }>('organization');
|
}).populate<{ organization: IOrganization }>('organization');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
39
backend/src/middleware/requireServiceAccountAuth.ts
Normal file
39
backend/src/middleware/requireServiceAccountAuth.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { ServiceAccount } from '../models';
|
||||||
|
import {
|
||||||
|
AccountNotFoundError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
|
||||||
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
const requireServiceAccountAuth = ({
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
|
location = 'params'
|
||||||
|
}: {
|
||||||
|
acceptedRoles: string[];
|
||||||
|
acceptedStatuses: string[];
|
||||||
|
location?: req;
|
||||||
|
}) => {
|
||||||
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const serviceAccountId = req[location].serviceAccountId;
|
||||||
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
|
|
||||||
|
// TODO: acceptedRoles and acceptedStatuses
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
return next(AccountNotFoundError({ message: 'Failed to locate Service Account' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceAccount.user.toString() !== req.user.id.toString()) {
|
||||||
|
return next(UnauthorizedRequestError({ message: 'Failed to authenticate the Service Account' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
req.serviceAccount = serviceAccount;
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default requireServiceAccountAuth;
|
||||||
@@ -10,6 +10,9 @@ import MembershipOrg, { IMembershipOrg } from './membershipOrg';
|
|||||||
import Organization, { IOrganization } from './organization';
|
import Organization, { IOrganization } from './organization';
|
||||||
import Secret, { ISecret } from './secret';
|
import Secret, { ISecret } from './secret';
|
||||||
import ServiceToken, { IServiceToken } from './serviceToken';
|
import ServiceToken, { IServiceToken } from './serviceToken';
|
||||||
|
import ServiceAccount, { IServiceAccount } from './serviceAccount'; // new
|
||||||
|
import ServiceAccountKey, { IServiceAccountKey } from './serviceAccountKey'; // new
|
||||||
|
import ServiceAccountPermission, { IServiceAccountPermission } from './serviceAccountPermission';
|
||||||
import TokenData, { ITokenData } from './tokenData';
|
import TokenData, { ITokenData } from './tokenData';
|
||||||
import User, { IUser } from './user';
|
import User, { IUser } from './user';
|
||||||
import UserAction, { IUserAction } from './userAction';
|
import UserAction, { IUserAction } from './userAction';
|
||||||
@@ -43,6 +46,12 @@ export {
|
|||||||
ISecret,
|
ISecret,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
IServiceToken,
|
IServiceToken,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccountKey,
|
||||||
|
IServiceAccountKey,
|
||||||
|
ServiceAccountPermission,
|
||||||
|
IServiceAccountPermission,
|
||||||
TokenData,
|
TokenData,
|
||||||
ITokenData,
|
ITokenData,
|
||||||
User,
|
User,
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Schema, model, Types, Document } from 'mongoose';
|
|
||||||
|
|
||||||
export interface IPermission extends Document {
|
|
||||||
_id: Types.ObjectId;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissionSchema = new Schema<IPermission>(
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timestamps: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const Permission = model<IPermission>('Permission', permissionSchema);
|
|
||||||
|
|
||||||
export default Permission;
|
|
||||||
@@ -3,9 +3,8 @@ import { Schema, model, Types, Document } from 'mongoose';
|
|||||||
export interface IServiceAccount extends Document {
|
export interface IServiceAccount extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
name: string;
|
name: string;
|
||||||
isActive: boolean;
|
|
||||||
organization: Types.ObjectId;
|
organization: Types.ObjectId;
|
||||||
createdBy: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
}
|
}
|
||||||
@@ -16,16 +15,12 @@ const serviceAccountSchema = new Schema<IServiceAccount>(
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
organization: {
|
organization: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Organization',
|
ref: 'Organization',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
createdBy: {
|
user: { // user who created the service account
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User',
|
ref: 'User',
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface IServiceAccountKey {
|
|||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceAccountSchema = new Schema<IServiceAccountKey>(
|
const serviceAccountKeySchema = new Schema<IServiceAccountKey>(
|
||||||
{
|
{
|
||||||
encryptedKey: {
|
encryptedKey: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -39,6 +39,6 @@ const serviceAccountSchema = new Schema<IServiceAccountKey>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ServiceAccountKey = model<IServiceAccountKey>('ServiceAccountKey', serviceAccountSchema);
|
const ServiceAccountKey = model<IServiceAccountKey>('ServiceAccountKey', serviceAccountKeySchema);
|
||||||
|
|
||||||
export default ServiceAccountKey;
|
export default ServiceAccountKey;
|
||||||
|
|||||||
37
backend/src/models/serviceAccountPermission.ts
Normal file
37
backend/src/models/serviceAccountPermission.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
|
|
||||||
|
export interface IServiceAccountPermission extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
serviceAccount: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
workspace?: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccountPermissionSchema = new Schema<IServiceAccountPermission>(
|
||||||
|
{
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace',
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: 'String'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ServiceAccountPermission = model<IServiceAccountPermission>('ServiceAccountPermission', serviceAccountPermissionSchema);
|
||||||
|
|
||||||
|
export default ServiceAccountPermission;
|
||||||
@@ -6,6 +6,7 @@ import workspace from './workspace';
|
|||||||
import secret from './secret'; // deprecated
|
import secret from './secret'; // deprecated
|
||||||
import secrets from './secrets';
|
import secrets from './secrets';
|
||||||
import serviceTokenData from './serviceTokenData';
|
import serviceTokenData from './serviceTokenData';
|
||||||
|
import serviceAccounts from './serviceAccounts';
|
||||||
import apiKeyData from './apiKeyData';
|
import apiKeyData from './apiKeyData';
|
||||||
import environment from "./environment"
|
import environment from "./environment"
|
||||||
import tags from "./tags"
|
import tags from "./tags"
|
||||||
@@ -19,6 +20,7 @@ export {
|
|||||||
secret,
|
secret,
|
||||||
secrets,
|
secrets,
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
|
serviceAccounts,
|
||||||
apiKeyData,
|
apiKeyData,
|
||||||
environment,
|
environment,
|
||||||
tags
|
tags
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
requireMembershipOrgAuth,
|
requireMembershipOrgAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body, param, query } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
|
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
|
||||||
import { organizationsController } from '../../controllers/v2';
|
import { organizationsController } from '../../controllers/v2';
|
||||||
|
|
||||||
@@ -77,4 +77,18 @@ router.get(
|
|||||||
organizationsController.getOrganizationWorkspaces
|
organizationsController.getOrganizationWorkspaces
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:organizationId/service-accounts',
|
||||||
|
param('organizationId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
organizationsController.getOrganizationServiceAccounts
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
53
backend/src/routes/v2/serviceAccounts.ts
Normal file
53
backend/src/routes/v2/serviceAccounts.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireOrganizationAuth,
|
||||||
|
requireServiceAccountAuth
|
||||||
|
} from '../../middleware';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
import {
|
||||||
|
OWNER,
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
ACCEPTED
|
||||||
|
} from '../../variables';
|
||||||
|
import { serviceAccountsController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
body('organizationId').exists().isString().trim(),
|
||||||
|
body('name').exists().isString().trim(),
|
||||||
|
body('publicKey').exists().isString().trim(),
|
||||||
|
body('expiresIn'), // measured in ms
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
location: 'body'
|
||||||
|
}),
|
||||||
|
serviceAccountsController.createServiceAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
// router.post(
|
||||||
|
// '/:serviceAccountId/key',
|
||||||
|
// body('workspaceId').exists().isString().trim(),
|
||||||
|
// body('encryptedKey').exists().isString().trim(),
|
||||||
|
// body('nonce').exists().isString().trim(),
|
||||||
|
// requireServiceAccountAuth({
|
||||||
|
// acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
// acceptedStatuses: [ACCEPTED]
|
||||||
|
// }),
|
||||||
|
// serviceAccountsController.addServiceAccountKey
|
||||||
|
// );
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:serviceAccountId/key/:serviceAccountKeyId',
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
// TODO: delete service account key id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
1
backend/src/types/express/index.d.ts
vendored
1
backend/src/types/express/index.d.ts
vendored
@@ -19,6 +19,7 @@ declare global {
|
|||||||
secrets: any;
|
secrets: any;
|
||||||
secretSnapshot: any;
|
secretSnapshot: any;
|
||||||
serviceToken: any;
|
serviceToken: any;
|
||||||
|
serviceAccount: any;
|
||||||
accessToken: any;
|
accessToken: any;
|
||||||
serviceTokenData: any;
|
serviceTokenData: any;
|
||||||
apiKeyData: any;
|
apiKeyData: any;
|
||||||
|
|||||||
Reference in New Issue
Block a user