refactor: Update GitHub token handling in Config.ts

The Config.ts file has been updated to handle the GitHub token more efficiently. The GetGitHubToken function now checks the repository type and throws an exception if a GitHub token is required but not provided. This change improves the security and reliability of the application by ensuring that the correct token is used for GitHub repositories.
This commit is contained in:
Simon Larsen 2024-06-12 11:31:50 +01:00
parent e719bb3b70
commit 64acf372d6
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
12 changed files with 200 additions and 16 deletions

View File

@ -0,0 +1,6 @@
enum CodeRepositoryType {
GitHub = "GitHub",
GitLab = "GitLab",
}
export default CodeRepositoryType;

11
Common/Utils/Enum.ts Normal file
View File

@ -0,0 +1,11 @@
import GenericObject from "../Types/GenericObject";
export default class EnumUtil {
public static isValidEnumValue<T extends GenericObject>(enumType: T, value: any): boolean {
return this.getValues(enumType).includes(value);
}
public static getValues<T extends GenericObject>(enumType: T): Array<string> {
return Object.values(enumType);
}
}

View File

@ -1,7 +1,9 @@
import ServiceRepository from 'Model/Models/ServiceRepository';
import UserMiddleware from '../Middleware/UserAuthorization';
import CodeRepositoryService, {
Service as CodeRepositoryServiceType,
} from '../Services/CodeRepositoryService';
import ServiceRepositoryService from '../Services/ServiceRepositoryService';
import {
ExpressRequest,
ExpressResponse,
@ -12,6 +14,7 @@ import BaseAPI from './BaseAPI';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import CodeRepository from 'Model/Models/CodeRepository';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
export default class CodeRepositoryAPI extends BaseAPI<
CodeRepository,
@ -44,17 +47,46 @@ export default class CodeRepositoryAPI extends BaseAPI<
},
select: {
name: true,
mainBranchName: true,
},
props: {
isRoot: true,
},
});
return Response.sendEntityResponse(
if(!codeRepository) {
throw new BadDataException('Code repository not found');
}
const servicesRepository: Array<ServiceRepository> = await ServiceRepositoryService.findBy({
query: {
codeRepositoryId: codeRepository.id!,
enablePullRequests: true,
},
select: {
serviceCatalog: {
name: true,
_id: true,
},
servicePathInRepository: true,
limitNumberOfOpenPullRequestsCount: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
});
return Response.sendJsonObjectResponse(
req,
res,
codeRepository,
CodeRepository
{
"codeRepository": CodeRepository.toJSON(codeRepository, CodeRepository),
"servicesRepository": ServiceRepository.toJSONArray(servicesRepository, ServiceRepository),
}
);
} catch (err) {
next(err);

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class MigrationName1718186569787 implements MigrationInterface {
public name = 'MigrationName1718186569787';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "CodeRepository" DROP COLUMN "mainBranchName"`
);
}
}

View File

@ -9,6 +9,7 @@ import { MigrationName1718101665865 } from './1718101665865-MigrationName';
import { MigrationName1718119926223 } from './1718119926223-MigrationName';
import { MigrationName1718124277321 } from './1718124277321-MigrationName';
import { MigrationName1718126316684 } from './1718126316684-MigrationName';
import { MigrationName1718186569787 } from './1718186569787-MigrationName';
export default [
InitialMigration,
@ -21,5 +22,6 @@ export default [
MigrationName1718101665865,
MigrationName1718119926223,
MigrationName1718124277321,
MigrationName1718126316684
MigrationName1718126316684,
MigrationName1718186569787,
];

View File

@ -1,3 +1,4 @@
ONEUPTIME_URL=https://oneuptime.com
ONEUPTIME_REPOSITORY_SECRET_KEY=your-repository-secret-key
ONEUPTIME_LOCAL_REPOSITORY_PATH=/repository
GITHUB_TOKEN=

View File

@ -1,4 +1,7 @@
import URL from 'Common/Types/API/URL';
import CodeRepositoryType from 'Common/Types/CodeRepository/CodeRepositoryType';
import BadDataException from 'Common/Types/Exception/BadDataException';
import EnumUtil from 'Common/Utils/Enum';
type GetStringFunction = () => string;
type GetURLFunction = () => URL;
@ -16,3 +19,30 @@ export const GetRepositorySecretKey: GetStringFunction = (): string => {
export const GetLocalRepositoryPath: GetStringFunction = (): string => {
return process.env['ONEUPTIME_LOCAL_REPOSITORY_PATH'] || '/repository';
};
export const GetRepositoryType: GetStringFunction = (): CodeRepositoryType => {
const repoType: string | undefined = process.env['REPOSITORY_TYPE'];
if(!repoType) {
return CodeRepositoryType.GitHub;
}
if(EnumUtil.isValidEnumValue(CodeRepositoryType, repoType)) {
return repoType as CodeRepositoryType;
}
// check if the repository type is valid and is from the values in the enum.
throw new BadDataException(`Invalid Repository Type ${repoType}. It should be one of ${EnumUtil.getValues(CodeRepositoryType).join(', ')}`);
};
export const GetGitHubToken: GetStringFunction = (): string => {
const token: string = process.env['GITHUB_TOKEN'] || '';
if(GetRepositoryType() === CodeRepositoryType.GitHub && !token) {
throw new BadDataException('GitHub Token is required for GitHub Repository');
}
return token;
}

View File

@ -1,22 +1,23 @@
import { GetLocalRepositoryPath } from './Config';
import CodeRepositoryUtil from './Utils/CodeRepository';
import CodeRepositoryUtil, { CodeRepositoryResult } from './Utils/CodeRepository';
import Dictionary from 'Common/Types/Dictionary';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import CodeRepositoryCommonServerUtil from 'CommonServer/Utils/CodeRepository/CodeRepository';
import CodeRepositoryFile from 'CommonServer/Utils/CodeRepository/CodeRepositoryFile';
import logger from 'CommonServer/Utils/Logger';
import CodeRepository from 'Model/Models/CodeRepository';
import dotenv from 'dotenv';
import InitUtil from './Utils/Init';
dotenv.config();
logger.info('OneUptime Copilot is starting...');
const init: PromiseVoidFunction = async (): Promise<void> => {
const codeRepository: CodeRepository =
await CodeRepositoryUtil.getCodeRepository();
logger.info(`Code Repository found: ${codeRepository.name}`);
await InitUtil.validate(); // validate all the configurations
const codeRepositoryResult: CodeRepositoryResult =
await CodeRepositoryUtil.getCodeRepository();
const allFiles: Dictionary<CodeRepositoryFile> =
await CodeRepositoryCommonServerUtil.getFilesInDirectoryRecursive({

View File

@ -3,12 +3,19 @@ import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import URL from 'Common/Types/API/URL';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import API from 'Common/Utils/API';
import logger from 'CommonServer/Utils/Logger';
import CodeRepositoryModel from 'Model/Models/CodeRepository';
import ServiceRepository from 'Model/Models/ServiceRepository';
export interface CodeRepositoryResult {
codeRepository: CodeRepositoryModel;
servicesRepository: Array<ServiceRepository>;
}
export default class CodeRepositoryUtil {
public static async getCodeRepository(): Promise<CodeRepositoryModel> {
public static async getCodeRepository(): Promise<CodeRepositoryResult> {
const repositorySecretKey: string = GetRepositorySecretKey();
if (!repositorySecretKey) {
@ -33,16 +40,42 @@ export default class CodeRepositoryUtil {
const codeRepository: CodeRepositoryModel =
CodeRepositoryModel.fromJSON(
codeRepositoryResult.data as JSONObject,
codeRepositoryResult.data['codeRepository'] as JSONObject,
CodeRepositoryModel
) as CodeRepositoryModel;
const servicesRepository: Array<ServiceRepository> =
(codeRepositoryResult.data['servicesRepository'] as JSONArray).map(
(serviceRepository: JSONObject) =>
ServiceRepository.fromJSON(
serviceRepository,
ServiceRepository
) as ServiceRepository
);
if (!codeRepository) {
throw new BadDataException(
'Code Repository not found with the secret key provided.'
);
}
return codeRepository;
if (!servicesRepository || servicesRepository.length === 0) {
throw new BadDataException(
'No services attached to this repository. Please attach services to this repository on OneUptime Dashboard.'
);
}
logger.info(`Code Repository found: ${codeRepository.name}`);
logger.info('Services found in the repository:');
servicesRepository.forEach((serviceRepository: ServiceRepository) => {
logger.info(`- ${serviceRepository.serviceCatalog?.name}`);
});
return {
codeRepository,
servicesRepository,
};
}
}

18
Copilot/Utils/Init.ts Normal file
View File

@ -0,0 +1,18 @@
import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType";
import { GetGitHubToken, GetRepositorySecretKey, GetRepositoryType } from "../Config";
import BadDataException from "Common/Types/Exception/BadDataException";
export default class InitUtil {
public static async validate(): Promise<void> {
// Check if the repository type is GitHub and the GitHub token is provided
if(GetRepositoryType() === CodeRepositoryType.GitHub && !GetGitHubToken()){
throw new BadDataException("GitHub token is required");
}
if(!GetRepositorySecretKey()){
throw new BadDataException("Repository Secret Key is required");
}
}
}

View File

@ -138,9 +138,7 @@ const ServiceRepositoryPage: FunctionComponent<PageComponentProps> = (
getElement: (item: ServiceRepository): ReactElement => {
if (!item['serviceCatalog']) {
throw new BadDataException(
'Service not found'
);
throw new BadDataException('Service not found');
}
return (

View File

@ -460,4 +460,39 @@ export default class CodeRepository extends BaseModel {
transformer: ObjectID.getDatabaseTransformer(),
})
public secretToken?: ObjectID = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateCodeRepository,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.ReadCodeRepository,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditCodeRepository,
],
})
@TableColumn({
required: true,
type: TableColumnType.ShortText,
canReadOnRelationQuery: true,
title: 'Main Branch Name',
description: 'Name of the main branch of this repository',
})
@Column({
nullable: false,
type: ColumnType.ShortText,
length: ColumnLength.ShortText,
})
public mainBranchName?: string = undefined;
}