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 | ||
|  |   }; | ||
|  | }; |