From 060ebf1b299afb9d5fa09b86d4dbe76e75286ca6 Mon Sep 17 00:00:00 2001 From: Phil Kunz Date: Wed, 9 Jan 2019 00:01:01 +0100 Subject: [PATCH] fix(core): update --- test/test.ts | 13 ++++-- ts/interfaces/cert.ts | 9 ++++ ts/interfaces/certremote.ts | 11 +++++ ts/interfaces/index.ts | 2 + ts/smartacme.classes.cert.ts | 15 +++--- ts/smartacme.classes.certmanager.ts | 33 +++++++++++-- ts/smartacme.classes.certremoteclient.ts | 39 +++++++++++++++- ts/smartacme.classes.certremotehandler.ts | 3 -- ts/smartacme.classes.smartacme.ts | 57 +++++++++++++++++++---- 9 files changed, 154 insertions(+), 28 deletions(-) create mode 100644 ts/interfaces/cert.ts create mode 100644 ts/interfaces/certremote.ts delete mode 100644 ts/smartacme.classes.certremotehandler.ts diff --git a/test/test.ts b/test/test.ts index f87dc76..8fc1ea4 100644 --- a/test/test.ts +++ b/test/test.ts @@ -11,20 +11,23 @@ tap.test('should create a valid instance of SmartAcme', async () => { smartAcmeInstance = new smartacme.SmartAcme({ accountEmail: 'domains@lossless.org', accountPrivateKey: null, + mongoDescriptor: { + mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'), + mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'), + mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL') + }, removeChallenge: async (...args) => { console.log(args); }, setChallenge: async (...args) => { console.log(args); }, - mongoDescriptor: { - mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'), - mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'), - mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL') + validateRemoteRequest: async () => { + return true; } }); await smartAcmeInstance.init(); - await smartAcmeInstance.getCertificateForDomain('bleu.de'); + // await smartAcmeInstance.getCertificateForDomain('bleu.de'); }); tap.start(); diff --git a/ts/interfaces/cert.ts b/ts/interfaces/cert.ts new file mode 100644 index 0000000..bc35e5a --- /dev/null +++ b/ts/interfaces/cert.ts @@ -0,0 +1,9 @@ +export type TCertStatus = 'existing' | 'nonexisting' | 'pending' | 'failed'; + +export interface ICert { + domainName: string; + created: number; + privateKey: string; + publicKey: string; + csr: string; +} \ No newline at end of file diff --git a/ts/interfaces/certremote.ts b/ts/interfaces/certremote.ts new file mode 100644 index 0000000..a3eb59e --- /dev/null +++ b/ts/interfaces/certremote.ts @@ -0,0 +1,11 @@ +import { ICert, TCertStatus } from './cert'; + +export interface ICertRemoteRequest { + secret: string; + domainName: string; +} + +export interface ICertRemoteResponse { + status: TCertStatus; + certificate?: ICert; +} diff --git a/ts/interfaces/index.ts b/ts/interfaces/index.ts index 28c0bbe..be29759 100644 --- a/ts/interfaces/index.ts +++ b/ts/interfaces/index.ts @@ -1 +1,3 @@ export * from './accountdata'; +export * from './cert'; +export * from './certremote'; diff --git a/ts/smartacme.classes.cert.ts b/ts/smartacme.classes.cert.ts index 4d04c13..87fa4c8 100644 --- a/ts/smartacme.classes.cert.ts +++ b/ts/smartacme.classes.cert.ts @@ -1,4 +1,7 @@ import * as plugins from './smartacme.plugins'; + +import * as interfaces from './interfaces'; + import { CertManager } from './smartacme.classes.certmanager'; import { Collection, svDb, unI } from '@pushrocks/smartdata'; @@ -6,24 +9,24 @@ import { Collection, svDb, unI } from '@pushrocks/smartdata'; @plugins.smartdata.Collection(() => { return CertManager.activeDB; }) -export class Cert extends plugins.smartdata.SmartDataDbDoc { +export class Cert extends plugins.smartdata.SmartDataDbDoc implements interfaces.ICert { @unI() public index: string; @svDb() - domainName: string; + public domainName: string; @svDb() - created: number; + public created: number; @svDb() - privateKey: string; + public privateKey: string; @svDb() - publicKey: string; + public publicKey: string; @svDb() - csr: string; + public csr: string; constructor(privateKeyArg: string, publicKeyArg: string, csrArg: string) { super(); diff --git a/ts/smartacme.classes.certmanager.ts b/ts/smartacme.classes.certmanager.ts index 310eca3..965ab0d 100644 --- a/ts/smartacme.classes.certmanager.ts +++ b/ts/smartacme.classes.certmanager.ts @@ -1,5 +1,8 @@ import * as plugins from './smartacme.plugins'; import { Cert } from './smartacme.classes.cert'; +import { SmartAcme } from './smartacme.classes.smartacme'; + +import * as interfaces from './interfaces'; export class CertManager { @@ -15,16 +18,22 @@ export class CertManager { private mongoDescriptor: plugins.smartdata.IMongoDescriptor; public smartdataDb: plugins.smartdata.SmartdataDb; - constructor(optionsArg: { + public pendingMap: plugins.lik.Stringmap; + + constructor(smartAcmeArg: SmartAcme,optionsArg: { mongoDescriptor: plugins.smartdata.IMongoDescriptor; }) { this.mongoDescriptor = optionsArg.mongoDescriptor; } public async init () { + // Smartdata DB this.smartdataDb = new plugins.smartdata.SmartdataDb(this.mongoDescriptor); await this.smartdataDb.init(); CertManager.activeDB = this.smartdataDb; + + // Pending Map + this.pendingMap = new plugins.lik.Stringmap(); }; /** @@ -33,6 +42,7 @@ export class CertManager { * @param domainName the domain Name to retrieve the vcertificate for */ public async retrieveCertificate(domainName: string): Promise { + await this.checkCerts(); const existingCertificate: Cert = await Cert.getInstance({ name: domainName }); @@ -56,12 +66,27 @@ export class CertManager { cert.save(); }; - public async deleteCertificate(domainName: string) { + public async deleteCertificate(domainNameArg: string) { - }; + } + + public async getCertificateStatus(domainNameArg: string): Promise { + const isPending = this.pendingMap.checkString('domainNameArg'); + if (isPending) { + return 'pending'; + } + + // otherwise lets continue + const existingCertificate = this.retrieveCertificate(domainNameArg); + if (existingCertificate) { + return 'existing'; + } + + return 'nonexisting'; + } /** * checks all certs for expiration */ - checkCerts() {} + private async checkCerts() {}; } diff --git a/ts/smartacme.classes.certremoteclient.ts b/ts/smartacme.classes.certremoteclient.ts index 67034e8..8c339c9 100644 --- a/ts/smartacme.classes.certremoteclient.ts +++ b/ts/smartacme.classes.certremoteclient.ts @@ -1,10 +1,47 @@ import * as plugins from './smartacme.plugins'; +import * as interfaces from './interfaces'; +import { ICertRemoteResponse } from './interfaces'; +// tslint:disable-next-line: max-classes-per-file export class CertRemoteClient { + private remoteUrl: string; + private secret: string; + constructor(optionsArg: { remoteUrl: string; secret: string; }) { - + this.remoteUrl = optionsArg.remoteUrl; + this.secret = optionsArg.secret; + } + + /** + * + * @param domainNameArg + */ + async getCertificateForDomain(domainNameArg: string): Promise { + let certificate: interfaces.ICert; + const doRequestCycle = async (): Promise => { + const response: ICertRemoteResponse = (await plugins.smartrequest.postJson(this.remoteUrl, { + requestBody: { + domainName: domainNameArg, + secret: this.secret + } + })).body; + switch(response.status) { + case 'pending': + await plugins.smartdelay.delayFor(5000); + const finalResponse = await doRequestCycle(); + return finalResponse; + case 'existing': + return response.certificate; + case 'failed': + default: + console.log(`could not retrieve certificate for ${domainNameArg}`); + return null; + } + }; + certificate = await doRequestCycle(); + return certificate; } } diff --git a/ts/smartacme.classes.certremotehandler.ts b/ts/smartacme.classes.certremotehandler.ts deleted file mode 100644 index 4011f9a..0000000 --- a/ts/smartacme.classes.certremotehandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as plugins from './smartacme.plugins'; - -export class CertRemoteHandler {} \ No newline at end of file diff --git a/ts/smartacme.classes.smartacme.ts b/ts/smartacme.classes.smartacme.ts index 3ba35e4..a05cd98 100644 --- a/ts/smartacme.classes.smartacme.ts +++ b/ts/smartacme.classes.smartacme.ts @@ -1,6 +1,8 @@ import * as plugins from './smartacme.plugins'; import { CertManager } from './smartacme.classes.certmanager'; -import { CertRemoteHandler } from './smartacme.classes.certremotehandler'; + +import * as interfaces from './interfaces'; +import { request } from 'http'; /** * @@ -8,9 +10,10 @@ import { CertRemoteHandler } from './smartacme.classes.certremotehandler'; export interface ISmartAcmeOptions { accountPrivateKey?: string; accountEmail: string; + mongoDescriptor: plugins.smartdata.IMongoDescriptor; setChallenge: (domainName: string, keyAuthorization: string) => Promise; removeChallenge: (domainName: string) => Promise; - mongoDescriptor: plugins.smartdata.IMongoDescriptor; + validateRemoteRequest: () => Promise; } export class SmartAcme { @@ -26,26 +29,64 @@ export class SmartAcme { // challenge fullfillment private setChallenge: (domainName: string, keyAuthorization: string) => Promise; private removeChallenge: (domainName: string) => Promise; + private validateRemoteRequest: () => Promise; // certmanager private certmanager: CertManager; - private certremoteHandler: CertRemoteHandler; + private certremoteHandler: plugins.smartexpress.Handler; constructor(optionsArg: ISmartAcmeOptions) { this.options = optionsArg; } + /** + * inits the instance + */ public async init() { - this.privateKey = this.options.accountPrivateKey || (await plugins.acme.forge.createPrivateKey()); + this.privateKey = + this.options.accountPrivateKey || (await plugins.acme.forge.createPrivateKey()); this.setChallenge = this.options.setChallenge; this.removeChallenge = this.options.removeChallenge; - this.certmanager = new CertManager({ + // CertMangaer + this.certmanager = new CertManager(this, { mongoDescriptor: this.options.mongoDescriptor }); await this.certmanager.init(); - this.certremoteHandler = new CertRemoteHandler(); + // CertRemoteHandler + this.certremoteHandler = new plugins.smartexpress.Handler('POST', async (req, res) => { + const requestBody: interfaces.ICertRemoteRequest = req.body; + const status: interfaces.TCertStatus = await this.certmanager.getCertificateStatus(requestBody.domainName); + const existingCertificate = await this.certmanager.retrieveCertificate( + requestBody.domainName + ); + let response: interfaces.ICertRemoteResponse; + switch (status) { + case 'existing': + response = { + status, + certificate: { + created: existingCertificate.created, + csr: existingCertificate.csr, + domainName: existingCertificate.domainName, + privateKey: existingCertificate.privateKey, + publicKey: existingCertificate.publicKey + } + }; + break; + default: + response = { + status + }; + break; + } + res.status(200); + res.send(response); + res.end(); + }); + + // ACME Client this.client = new plugins.acme.Client({ directoryUrl: plugins.acme.directory.letsencrypt.staging, accountKey: this.privateKey @@ -63,7 +104,7 @@ export class SmartAcme { const retrievedCertificate = await this.certmanager.retrieveCertificate(domain); - if(retrievedCertificate) { + if (retrievedCertificate) { return retrievedCertificate; } @@ -123,6 +164,4 @@ export class SmartAcme { this.certmanager.storeCertificate(key.toString(), cert.toString(), csr.toString()); } - - toStorageObject() {} }