Skip to content

Commit

Permalink
Encapsulate encoding/decoding of RemoteConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
ulrikandersen committed Dec 4, 2024
1 parent 6919703 commit d6510d5
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { NextRequest, NextResponse } from "next/server"
import { encryptionService, session } from "@/composition"
import { remoteConfigEncoder, session } from "@/composition"
import { env, makeAPIErrorResponse, makeUnauthenticatedAPIErrorResponse } from "@/common"
import { downloadFile, checkIfJsonOrYaml, ErrorName } from "@/common/utils/fileUtils";
import { RemoteConfigSchema } from "@/features/projects/domain/RemoteConfig";

interface RemoteSpecificationParams {
encryptedRemoteConfig: string // encrypted and URL encoded JSON string
encodedRemoteConfig: string
}

export async function GET(req: NextRequest, { params }: { params: RemoteSpecificationParams }) {
Expand All @@ -14,11 +13,7 @@ export async function GET(req: NextRequest, { params }: { params: RemoteSpecific
return makeUnauthenticatedAPIErrorResponse()
}

const decodedEncryptedRemoteConfig = Buffer.from(params.encryptedRemoteConfig, 'base64').toString('utf-8');

const decryptedRemoteConfig = encryptionService.decrypt(decodedEncryptedRemoteConfig)

const remoteConfig = RemoteConfigSchema.parse(JSON.parse(decryptedRemoteConfig))
const remoteConfig = remoteConfigEncoder.decode(params.encodedRemoteConfig)

let url: URL
try {
Expand Down
8 changes: 6 additions & 2 deletions src/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from "@/features/hooks/domain"
import { RepoRestrictedGitHubClient } from "./common/github/RepoRestrictedGitHubClient"
import RsaEncryptionService from "./common/encryption/EncryptionService"
import RemoteConfigEncoder from "./features/projects/domain/RemoteConfigEncoder"

const gitHubAppCredentials = {
appId: env.getOrThrow("GITHUB_APP_ID"),
Expand Down Expand Up @@ -177,11 +178,13 @@ export const projectRepository = new ProjectRepository({
repository: projectUserDataRepository
})

export const encryptionService = new RsaEncryptionService({
const encryptionService = new RsaEncryptionService({
publicKey: Buffer.from(env.getOrThrow("ENCRYPTION_PUBLIC_KEY_BASE_64"), "base64").toString("utf-8"),
privateKey: Buffer.from(env.getOrThrow("ENCRYPTION_PRIVATE_KEY_BASE_64"), "base64").toString("utf-8")
})

export const remoteConfigEncoder = new RemoteConfigEncoder(encryptionService)

export const projectDataSource = new CachingProjectDataSource({
dataSource: new GitHubProjectDataSource({
repositoryDataSource: new FilteringGitHubRepositoryDataSource({
Expand All @@ -196,7 +199,8 @@ export const projectDataSource = new CachingProjectDataSource({
})
}),
repositoryNameSuffix: env.getOrThrow("REPOSITORY_NAME_SUFFIX"),
encryptionService: encryptionService
encryptionService: encryptionService,
remoteConfigEncoder: remoteConfigEncoder
}),
repository: projectRepository
})
Expand Down
11 changes: 7 additions & 4 deletions src/features/projects/data/GitHubProjectDataSource.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IEncryptionService } from "@/common/encryption/EncryptionService"
import {
Project,
Version,
Expand All @@ -9,22 +10,25 @@ import {
GitHubRepository,
GitHubRepositoryRef
} from "../domain"
import { IEncryptionService } from "@/common/encryption/EncryptionService"
import RemoteConfig from "../domain/RemoteConfig"
import RemoteConfigEncoder from "../domain/RemoteConfigEncoder"

export default class GitHubProjectDataSource implements IProjectDataSource {
private readonly repositoryDataSource: IGitHubRepositoryDataSource
private readonly repositoryNameSuffix: string
private readonly encryptionService: IEncryptionService
private readonly remoteConfigEncoder: RemoteConfigEncoder

constructor(config: {
repositoryDataSource: IGitHubRepositoryDataSource
repositoryNameSuffix: string
encryptionService: IEncryptionService
remoteConfigEncoder: RemoteConfigEncoder
}) {
this.repositoryDataSource = config.repositoryDataSource
this.repositoryNameSuffix = config.repositoryNameSuffix
this.encryptionService = config.encryptionService
this.remoteConfigEncoder = config.remoteConfigEncoder
}

async getProjects(): Promise<Project[]> {
Expand Down Expand Up @@ -181,13 +185,12 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
} : undefined
};

// Encrypt and encode remote config
const encryptedRemoteConfig = Buffer.from(this.encryptionService.encrypt(JSON.stringify(remoteConfig))).toString('base64');
const encodedRemoteConfig = this.remoteConfigEncoder.encode(remoteConfig);

return {
id: this.makeURLSafeID((e.id || e.name).toLowerCase()),
name: e.name,
url: `/api/remotes/${encryptedRemoteConfig}`
url: `/api/remotes/${encodedRemoteConfig}`
};
})
versions.push({
Expand Down
29 changes: 29 additions & 0 deletions src/features/projects/domain/RemoteConfigEncoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { IEncryptionService } from "@/common/encryption/EncryptionService";
import RemoteConfig, { RemoteConfigSchema } from "./RemoteConfig";

/**
* Encodes and decodes remote configs.
*
* The remote config is first stringified to JSON, then encrypted, and finally encoded in base64.
*
* At the receiving end, the encoded string is first decoded from base64, then decrypted, and finally parsed as JSON.
*/
export default class RemoteConfigEncoder {
private readonly encryptionService: IEncryptionService;

constructor(encryptionService: IEncryptionService) {
this.encryptionService = encryptionService;
}

encode(remoteConfig: RemoteConfig): string {
const jsonString = JSON.stringify(remoteConfig);
const encryptedString = this.encryptionService.encrypt(jsonString);
return Buffer.from(encryptedString).toString('base64');
}

decode(encodedString: string): RemoteConfig {
const decodedString = Buffer.from(encodedString, 'base64').toString('utf-8');
const decryptedString = this.encryptionService.decrypt(decodedString);
return RemoteConfigSchema.parse(JSON.parse(decryptedString));
}
}

0 comments on commit d6510d5

Please sign in to comment.