123 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| // Much of this file was based on the `le-challenge-ddns` library (which we are not using
 | |
| // here because it's method of setting records requires things we don't really want).
 | |
| module.exports.create = function (deps, conf, utils) {
 | |
| 
 | |
|   function getReleventSessionId(domain) {
 | |
|     var sessId;
 | |
| 
 | |
|     utils.iterateAllModules(function (mod, domainList) {
 | |
|       // We return a truthy value in these cases because of the way the iterate function
 | |
|       // handles modules grouped by domain. By returning true we are saying these domains
 | |
|       // are "handled" and so if there are multiple modules we won't be given the rest.
 | |
|       if (sessId) { return true; }
 | |
|       if (domainList.indexOf(domain) < 0) { return true; }
 | |
| 
 | |
|       // But if the domains are relevant but we don't know how to handle the module we
 | |
|       // return false to allow us to look at any other modules that might exist here.
 | |
|       if (mod.type !== 'dns@oauth3.org')  { return false; }
 | |
| 
 | |
|       sessId = mod.tokenId || mod.token_id;
 | |
|       return true;
 | |
|     });
 | |
| 
 | |
|     return sessId;
 | |
|   }
 | |
| 
 | |
|   function get(args, domain, challenge, done) {
 | |
|     done(new Error("Challenge.get() does not need an implementation for dns-01. (did you mean Challenge.loopback?)"));
 | |
|   }
 | |
|   // same as get, but external
 | |
|   function loopback(args, domain, challenge, done) {
 | |
|     var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain;
 | |
|     require('dns').resolveTxt(challengeDomain, done);
 | |
|   }
 | |
| 
 | |
|   var activeChallenges = {};
 | |
|   async function removeAsync(args, domain) {
 | |
|     var data = activeChallenges[domain];
 | |
|     if (!data) {
 | |
|       console.warn(new Error('cannot remove DNS challenge for ' + domain + ': already removed'));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var session = await utils.getSession(data.sessId);
 | |
|     var directives = await deps.OAUTH3.discover(session.token.aud);
 | |
|     var apiOpts = {
 | |
|       api: 'dns.unset'
 | |
|     , session: session
 | |
|     , type: 'TXT'
 | |
|     , value: data.keyAuthDigest
 | |
|     };
 | |
|     await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, data.splitDomain));
 | |
| 
 | |
|     delete activeChallenges[domain];
 | |
|   }
 | |
|   async function setAsync(args, domain, challenge, keyAuth) {
 | |
|     if (activeChallenges[domain]) {
 | |
|       await removeAsync(args, domain, challenge);
 | |
|     }
 | |
| 
 | |
|     var sessId = getReleventSessionId(domain);
 | |
|     if (!sessId) {
 | |
|       throw new Error('no DDNS module handles the domain ' + domain);
 | |
|     }
 | |
|     var session = await utils.getSession(sessId);
 | |
|     var directives = await deps.OAUTH3.discover(session.token.aud);
 | |
| 
 | |
|     // I'm not sure what role challenge is supposed to play since even in the library
 | |
|     // this code is based on it was never used, but check for it anyway because ...
 | |
|     if (!challenge || keyAuth) {
 | |
|       console.warn(new Error('DDNS challenge missing challenge or keyAuth'));
 | |
|     }
 | |
|     var keyAuthDigest = require('crypto').createHash('sha256').update(keyAuth || '').digest('base64')
 | |
|       .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
 | |
| 
 | |
|     var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain;
 | |
|     var splitDomain = (await utils.splitDomains(directives.api, [challengeDomain]))[0];
 | |
| 
 | |
|     var apiOpts = {
 | |
|       api: 'dns.set'
 | |
|     , session: session
 | |
|     , type: 'TXT'
 | |
|     , value: keyAuthDigest
 | |
|     , ttl: args.ttl || 0
 | |
|     };
 | |
|     await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, splitDomain));
 | |
| 
 | |
|     activeChallenges[domain] = {
 | |
|       sessId
 | |
|     , keyAuthDigest
 | |
|     , splitDomain
 | |
|     };
 | |
| 
 | |
|     return new Promise(res => setTimeout(res, 1000));
 | |
|   }
 | |
| 
 | |
|   // It might be slightly easier to use arguments and apply, but the library that will use
 | |
|   // this function counts the arguments we expect.
 | |
|   function set(a, b, c, d, done) {
 | |
|     setAsync(a, b, c, d).then(result => done(null, result), done);
 | |
|   }
 | |
|   function remove(a, b, c, done) {
 | |
|     removeAsync(a, b, c).then(result => done(null, result), done);
 | |
|   }
 | |
| 
 | |
|   function getOptions() {
 | |
|     return {
 | |
|       oauth3: 'oauth3.org'
 | |
|     , debug: conf.debug
 | |
|     , acmeChallengeDns: '_acme-challenge.'
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     getOptions
 | |
|   , set
 | |
|   , get
 | |
|   , remove
 | |
|   , loopback
 | |
|   };
 | |
| };
 |