From f5b0d80b7621d05d794ba2884a47c9cc720ed9f8 Mon Sep 17 00:00:00 2001 From: Robert Cronin Date: Wed, 14 Oct 2020 12:08:54 +0800 Subject: [PATCH] wip --- openapi.yaml | 10 ++- src/Polykey.ts | 22 ++--- src/agent/PolykeyAgent.ts | 4 +- src/api/HttpApi.ts | 167 +++++++++++++++++++++++--------------- 4 files changed, 120 insertions(+), 83 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index acab250e54..f855f02bf0 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -206,6 +206,10 @@ paths: text/plain: schema: type: string + application/octet-stream: + schema: + type: string + format: binary "401": description: Not authenticated "403": @@ -222,11 +226,15 @@ paths: - bearerAuth: [admin, write_secrets] - OAuth2-Client: [admin, write_secrets] requestBody: - description: Object describing the secret + description: Secret content content: text/plain: schema: type: string + application/octet-stream: + schema: + type: string + format: binary responses: "200": description: Secret was created successfully diff --git a/src/Polykey.ts b/src/Polykey.ts index ea279f173a..b0f1b06753 100644 --- a/src/Polykey.ts +++ b/src/Polykey.ts @@ -52,25 +52,15 @@ class Polykey { }).bind(this), ((vaultName: string, secretName: string) => { const vault = this.vaultManager.getVault(vaultName) - return vault.getSecret(secretName).toString() + return vault.getSecret.bind(vault)(secretName) }).bind(this), - (async (vaultName: string, secretName: string, secretContent: string) => { - try { - const vault = this.vaultManager.getVault(vaultName) - await vault.addSecret(secretName, Buffer.from(secretContent)) - return true - } catch (error) { - return false - } + (async (vaultName: string, secretName: string, secretContent: Buffer) => { + const vault = this.vaultManager.getVault(vaultName) + await vault.addSecret(secretName, secretContent) }).bind(this), (async (vaultName: string, secretName: string) => { - try { - const vault = this.vaultManager.getVault(vaultName) - await vault.removeSecret(secretName) - return true - } catch (error) { - return false - } + const vault = this.vaultManager.getVault(vaultName) + await vault.removeSecret(secretName) }).bind(this), ); } diff --git a/src/agent/PolykeyAgent.ts b/src/agent/PolykeyAgent.ts index 5c330da0f9..09076da0af 100644 --- a/src/agent/PolykeyAgent.ts +++ b/src/agent/PolykeyAgent.ts @@ -141,7 +141,7 @@ class PolykeyAgent implements IAgentServer { private noThrowRefreshTimeout() { try { this.pk.keyManager.refreshTimeout(); - } catch (error) {} + } catch (error) { } } async addPeer( @@ -863,7 +863,7 @@ class PolykeyAgent implements IAgentServer { if (this.pk.keyManager.identityLoaded) { this.pk.keyManager.refreshTimeout(timeout); } else { - await this.pk.keyManager.unlockIdentity(passphrase,timeout); + await this.pk.keyManager.unlockIdentity(passphrase, timeout); } // re-load all meta data diff --git a/src/api/HttpApi.ts b/src/api/HttpApi.ts index f82c796142..972a0fd94d 100644 --- a/src/api/HttpApi.ts +++ b/src/api/HttpApi.ts @@ -4,7 +4,7 @@ import path from 'path'; import http from 'http'; import https from 'https'; import jsyaml from 'js-yaml'; -import express from 'express'; +import express, { RequestHandler } from 'express'; import passport from 'passport' import { getPort } from '../utils'; import session from 'express-session' @@ -19,6 +19,7 @@ import { User, Client } from './AuthorizationServer/OAuth2Store'; import { Strategy as BearerStrategy } from 'passport-http-bearer'; import { TLSCredentials } from '../keys/pki/PublicKeyInfrastructure'; import { Strategy as ClientPasswordStrategy } from 'passport-oauth2-client-password'; +import { DEFAULT_ENCODING } from 'crypto'; class HttpApi { private openApiPath: string @@ -31,9 +32,9 @@ class HttpApi { private newVault: (vaultName: string) => Promise; private deleteVault: (vaultName: string) => Promise; private listSecrets: (vaultName: string) => string[]; - private getSecret: (vaultName: string, secretName: string) => string; - private newSecret: (vaultName: string, secretName: string, secretContent: string) => Promise; - private deleteSecret: (vaultName: string, secretName: string) => Promise; + private getSecret: (vaultName: string, secretName: string) => Buffer; + private newSecret: (vaultName: string, secretName: string, secretContent: Buffer) => Promise; + private deleteSecret: (vaultName: string, secretName: string) => Promise; private tlsCredentials: TLSCredentials private oauth: OAuth2 @@ -50,9 +51,9 @@ class HttpApi { newVault: (vaultName: string) => Promise, deleteVault: (vaultName: string) => Promise, listSecrets: (vaultName: string) => string[], - getSecret: (vaultName: string, secretName: string) => string, - newSecret: (vaultName: string, secretName: string, secretContent: string) => Promise, - deleteSecret: (vaultName: string, secretName: string) => Promise, + getSecret: (vaultName: string, secretName: string) => Buffer, + newSecret: (vaultName: string, secretName: string, secretContent: string | Buffer) => Promise, + deleteSecret: (vaultName: string, secretName: string) => Promise, ) { // this code is needed as we can't require yaml files const fromSrcFolderPath = path.join(__dirname, '../../openapi.yaml') @@ -109,7 +110,7 @@ class HttpApi { this.expressServer.use(passport.session()); // redirect from base url to docs - this.expressServer.get('/',(req, res, next) => { + this.expressServer.get('/', (req, res, next) => { res.redirect('/docs') }) @@ -261,120 +262,158 @@ class HttpApi { } // === openapi endpoints === // - private handleCertificateSigningRequest = async (req, res, next) => { + private handleRootCertificateRequest: RequestHandler = async (req, res, next) => { try { - const body = req.body; - const response = this.handleCSR(body); - this.writeJson(res, response); + const response = this.getRootCertificate(); + this.writeString(res, response); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleRootCertificateRequest = async (req, res, next) => { + private handleCertificateChainRequest: RequestHandler = async (req, res, next) => { try { - const response = this.getRootCertificate(); - this.writeJson(res, response); + const response = this.getCertificateChain(); + this.writeStringList(res, response); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleCertificateChainRequest = async (req, res, next) => { + private handleCertificateSigningRequest: RequestHandler = async (req, res, next) => { try { - const response = this.getCertificateChain(); - this.writeJson(res, response); + const body = req.body; + const response = this.handleCSR(body); + this.writeString(res, response); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleVaultsListRequest = async (req, res, next) => { + private handleVaultsListRequest: RequestHandler = async (req, res, next) => { try { const response = this.getVaultNames() - this.writeJson(res, response); + this.writeStringList(res, response); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleNewVaultRequest = async (req, res, next) => { + private handleNewVaultRequest: RequestHandler = async (req, res, next) => { try { - const vaultName = req.openapi.pathParams.vaultName; + const vaultName = (req).openapi.pathParams.vaultName; await this.newVault(vaultName) - this.writeJson(res); + this.writeSuccess(res); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleDeleteVaultRequest = async (req, res, next) => { + private handleDeleteVaultRequest: RequestHandler = async (req, res, next) => { try { - const vaultName = req.openapi.pathParams.vaultName; + const vaultName = (req).openapi.pathParams.vaultName; await this.deleteVault(vaultName) - this.writeJson(res); + this.writeSuccess(res); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleSecretsListRequest = async (req, res, next) => { + private handleSecretsListRequest: RequestHandler = async (req, res, next) => { try { - const vaultName = req.openapi.pathParams.vaultName; + const vaultName = (req).openapi.pathParams.vaultName; const response = this.listSecrets(vaultName); - this.writeJson(res, response); + this.writeStringList(res, response); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleGetSecretRequest = async (req, res, next) => { + private handleGetSecretRequest: RequestHandler = async (req, res, next) => { try { - const vaultName = req.openapi.pathParams.vaultName; - const secretName = req.openapi.pathParams.secretName; + + const vaultName = (req).openapi.pathParams.vaultName; + const secretName = (req).openapi.pathParams.secretName; const response = this.getSecret(vaultName, secretName) - this.writeJson(res, response); + + const accepts = req.accepts()[0] + if (accepts == 'text/plain') { + this.writeString(res, response.toString()) + } else if (accepts == 'application/octet-stream') { + this.writeBinary(res, secretName, response); + } else { + throw Error('MIME type not supported') + } } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleNewSecretRequest = async (req, res, next) => { + private handleNewSecretRequest: RequestHandler = async (req, res, next) => { try { - const vaultName = req.openapi.pathParams.vaultName; - const secretName = req.openapi.pathParams.secretName; - const secretContent = req.body + const vaultName = (req).openapi.pathParams.vaultName; + const secretName = (req).openapi.pathParams.secretName; + + let secretContent: Buffer + const contentType = req.headers['content-type'] + if (contentType == 'text/plain') { + secretContent = Buffer.from(req.body) + } else if (contentType == 'application/octet-stream') { + secretContent = await new Promise((resolve, reject) => { + const bufferList: Buffer[] = [] + req.on('data', (data) => bufferList.push(data)) + req.on('error', (err) => reject(err)) + req.on('end', () => resolve(Buffer.concat(bufferList))) + }) + } else { + throw Error('MIME type not supported') + } + await this.newSecret(vaultName, secretName, secretContent); - this.writeJson(res); + this.writeSuccess(res); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; - private handleDeleteSecretRequest = async (req, res, next) => { + private handleDeleteSecretRequest: RequestHandler = async (req, res, next) => { try { - const vaultName = req.openapi.pathParams.vaultName; - const secretName = req.openapi.pathParams.secretName; + const vaultName = (req).openapi.pathParams.vaultName; + const secretName = (req).openapi.pathParams.secretName; await this.deleteSecret(vaultName, secretName); - this.writeJson(res); + this.writeSuccess(res); } catch (error) { - this.writeJson(res, error); + this.writeError(res, error); } }; // === Helper methods === // - private writeJson(response: http.ServerResponse, payload?: string | Object | Error | Array, code: number = 200) { - let responseString: string | undefined - if (!payload) { - responseString = undefined - } else if (payload instanceof Error) { - code = 500 - responseString = JSON.stringify({ error: payload.message }, null, 2); - } else { - responseString = JSON.stringify(payload, null, 2); - } - response.writeHead(code, { 'Content-Type': 'application/json' }); - response.end(responseString); + private writeSuccess(res: http.ServerResponse) { + res.writeHead(200); + res.end(); + } + private writeError(res: http.ServerResponse, error: Error) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: error.message }, null, 2)); + } + private writeString(res: http.ServerResponse, text: string) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(text); + } + private writeStringList(res: http.ServerResponse, list: string[]) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(list, null, 2)); + } + private writeJson(res: http.ServerResponse, payload: Object) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(payload, null, 2)); + } + private writeBinary(res: http.ServerResponse, filename: string, payload: Buffer) { + res.writeHead(200, { + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': `file; filename="${filename}"` + }); + res.end(payload, 'binary'); } private checkScope(scope: string[]) {