9781cd65b05088664a1d1dee04282d12ff31bf05
2 /* eslint-disable capitalized-comments, sonarjs/no-duplicate-string */
6 const assert
= require('assert');
7 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
9 const Communication
= require('../../lib/communication');
10 const { ValidationError
} = require('../../lib/errors');
11 const dns
= require('dns');
13 const stubLogger
= require('../stub-logger');
14 const testData
= require('../test-data/communication');
16 const noExpectedException
= 'did not get expected exception';
18 describe('Communication', function () {
19 let communication
, options
;
21 beforeEach(function () {
23 communication
= new Communication(stubLogger
, options
);
25 sinon
.stub(communication
, 'axios');
27 afterEach(function () {
31 it('instantiates', function () {
32 assert(communication
);
35 it('covers no config', function () {
36 communication
= new Communication(stubLogger
);
39 describe('Axios timing coverage', function () {
44 it('tags request', function () {
45 communication
.axios
.interceptors
.request
.handlers
[0].fulfilled(request
);
46 assert(request
.startTimestampMs
);
48 it('tags response', function () {
49 communication
.axios
.interceptors
.response
.handlers
[0].fulfilled(response
);
50 assert(response
.elapsedTimeMs
);
52 }); // Axios timing coverage
54 describe('_challengeFromVerifier', function () {
55 it('covers', function () {
56 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
57 const expected
= 'O5W5A-1CAnrNGp2yHZtEql6rfHere4wJmzsyow7LLiY';
58 const result
= Communication
._challengeFromVerifier(verifier
);
59 assert
.strictEqual(result
, expected
);
61 }); // _challengeFromVerifier
63 describe('generatePKCE', function () {
64 it('covers', async
function () {
65 const result
= await Communication
.generatePKCE();
66 assert(result
.codeVerifier
);
67 assert(result
.codeChallenge
);
68 assert(result
.codeChallengeMethod
);
69 assert
.strictEqual(result
.codeChallengeMethod
, 'S256');
71 it('covers error', async
function () {
73 await Communication
.generatePKCE(1);
74 assert
.fail(noExpectedException
);
76 assert(e
instanceof RangeError
);
81 describe('verifyChallenge', function () {
82 it('covers success', function () {
83 const method
= 'S256';
84 const challenge
= 'O5W5A-1CAnrNGp2yHZtEql6rfHere4wJmzsyow7LLiY';
85 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
86 const result
= Communication
.verifyChallenge(challenge
, verifier
, method
);
87 assert
.strictEqual(result
, true);
89 it('also covers success', function () {
90 const method
= 'SHA256';
91 const challenge
= 'O5W5A-1CAnrNGp2yHZtEql6rfHere4wJmzsyow7LLiY';
92 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
93 const result
= Communication
.verifyChallenge(challenge
, verifier
, method
);
94 assert
.strictEqual(result
, true);
96 it('covers failure', function () {
97 const method
= 'S256';
98 const challenge
= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
99 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
100 const result
= Communication
.verifyChallenge(challenge
, verifier
, method
);
101 assert
.strictEqual(result
, false);
103 it('covers unhandled method', function () {
104 const method
= 'MD5';
105 const challenge
= 'xkfP7DUYDsnu07Kg6ogc8A';
106 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
108 Communication
.verifyChallenge(challenge
, verifier
, method
);
109 assert
.fail(noExpectedException
);
111 assert(e
.message
.includes('unsupported'));
114 }); // verifyChallenge
116 describe('_userAgentString', function () {
117 it('has default behavior', function () {
118 const result
= Communication
._userAgentString();
120 assert(result
.length
> 30);
122 it('is settable', function () {
123 const result
= Communication
._userAgentString({
126 implementation: 'custom',
129 assert
.strictEqual(result
, 'myClient/9.9.9 (custom)');
131 it('covers branches', function () {
132 const result
= Communication
._userAgentString({
138 assert
.strictEqual(result
, 'myClient/9.9.9');
140 }); // userAgentString
142 describe('Axios Configurations', function () {
143 let requestUrl
, expectedUrl
;
144 beforeEach(function () {
145 requestUrl
= 'https://example.com/client_id';
146 expectedUrl
= 'https://example.com/client_id';
148 it('_axiosConfig', function () {
149 const method
= 'GET';
150 const contentType
= 'text/plain';
151 const body
= undefined;
153 'extra_parameter': 'foobar',
155 const urlObj
= new URL(requestUrl
);
156 const expectedUrlObj
= new URL(`${requestUrl}?extra_parameter=foobar`);
159 url: 'https://example.com/client_id',
161 'Content-Type': 'text/plain',
163 params: expectedUrlObj
.searchParams
,
164 responseType: 'text',
165 validateStatus: Communication
._validateStatus
,
167 const result
= Communication
._axiosConfig(method
, urlObj
, body
, params
, {
168 'Content-Type': contentType
,
170 delete result
.transformResponse
;
171 assert
.deepStrictEqual(result
, expected
);
173 it('_axiosConfig covers defaults', function () {
174 const method
= 'OPTIONS';
175 const urlObj
= new URL(requestUrl
);
176 const expectedUrlObj
= new URL(requestUrl
);
181 params: expectedUrlObj
.searchParams
,
182 responseType: 'text',
183 validateStatus: Communication
._validateStatus
,
185 const result
= Communication
._axiosConfig(method
, urlObj
);
186 delete result
.transformResponse
;
187 assert
.deepStrictEqual(result
, expected
);
189 it('covers data', function () {
190 const method
= 'POST';
191 const body
= Buffer
.from('some data');
193 const urlObj
= new URL(requestUrl
);
196 url: 'https://example.com/client_id',
199 params: urlObj
.searchParams
,
200 responseType: 'text',
201 validateStatus: Communication
._validateStatus
,
203 const result
= Communication
._axiosConfig(method
, urlObj
, body
, params
, {});
204 delete result
.transformResponse
;
205 assert
.deepStrictEqual(result
, expected
);
207 it('covers null response transform', function () {
208 const urlObj
= new URL(requestUrl
);
209 const result
= Communication
._axiosConfig('GET', urlObj
, undefined, {}, {});
210 result
.transformResponse
[0]();
213 describe('_validateStatus', function () {
214 it('allows normal valid', function () {
215 const result
= Communication
._validateStatus(200);
216 assert
.strictEqual(result
, true);
218 it('allows unauthorized', function () {
219 const result
= Communication
._validateStatus(401);
220 assert
.strictEqual(result
, true);
222 it('rejects invalid', function () {
223 const result
= Communication
._validateStatus(400);
224 assert
.strictEqual(result
, false);
226 }); // _validateStatus
227 }); // Axios Configurations
229 describe('_baseUrlString', function () {
230 it('covers no path', function () {
231 const urlObj
= new URL('https://example.com');
232 const expected
= 'https://example.com/';
233 const result
= Communication
._baseUrlString(urlObj
);
234 assert
.strictEqual(result
, expected
);
236 it('covers paths', function () {
237 const urlObj
= new URL('https://example.com/path/blah');
238 const expected
= 'https://example.com/path/';
239 const result
= Communication
._baseUrlString(urlObj
);
240 assert
.strictEqual(result
, expected
);
242 }); // _baseUrlString
244 describe('_parseContentType', function () {
245 let contentTypeHeader
, expected
, result
;
246 it('covers undefined', function () {
247 contentTypeHeader
= undefined;
249 mediaType: 'application/octet-stream',
252 result
= Communication
._parseContentType(contentTypeHeader
);
253 assert
.deepStrictEqual(result
, expected
);
255 it('covers empty', function () {
256 contentTypeHeader
= '';
258 mediaType: 'application/octet-stream',
261 result
= Communication
._parseContentType(contentTypeHeader
);
262 assert
.deepStrictEqual(result
, expected
);
264 it('covers extra parameters', function () {
265 contentTypeHeader
= 'text/plain; CharSet="UTF-8"; WeirdParam';
267 mediaType: 'text/plain',
270 'weirdparam': undefined,
273 result
= Communication
._parseContentType(contentTypeHeader
);
274 assert
.deepStrictEqual(result
, expected
);
276 }); // parseContentType
278 describe('_mergeLinkHeader', function () {
279 let microformat
, response
, expected
;
280 beforeEach(function () {
284 link: '<https://example.com/>; rel="self", <https://hub.example.com/>;rel="hub"',
289 it('covers', function () {
293 'hub': ['https://hub.example.com/'],
294 'self': ['https://example.com/'],
297 'https://example.com/': {
301 'https://hub.example.com/': {
307 communication
._mergeLinkHeader(microformat
, response
);
308 assert
.deepStrictEqual(microformat
, expected
);
310 it('covers existing', function () {
314 'preload': ['https://example.com/style'],
315 'hub': ['https://hub.example.com/'],
318 'https://hub.example.com/': {
322 'https://example.com/style': {
331 'preload': ['https://example.com/style'],
332 'hub': ['https://hub.example.com/', 'https://hub.example.com/'],
333 'self': ['https://example.com/'],
336 'https://example.com/': {
340 'https://hub.example.com/': {
341 rels: ['hub', 'hub'],
344 'https://example.com/style': {
350 communication
._mergeLinkHeader(microformat
, response
);
351 assert
.deepStrictEqual(microformat
, expected
);
353 it('ignores bad header', function () {
354 response
.headers
.link
= 'not really a link header';
360 communication
._mergeLinkHeader(microformat
, response
);
361 assert
.deepStrictEqual(microformat
, expected
);
363 }); // _mergeLinkHeader
365 describe('fetchMicroformat', function () {
366 let expected
, response
, result
, urlObj
;
367 beforeEach(function () {
368 expected
= undefined;
370 urlObj
= new URL('https://thuza.ratfeathers.com/');
372 headers: Object
.assign({}, testData
.linkHeaders
),
373 data: testData
.hCardHtml
,
376 it('covers', async
function () {
377 response
.data
= testData
.hCardHtml
;
378 communication
.axios
.resolves(response
);
381 'authorization_endpoint': ['https://ia.squeep.com/auth'],
382 'token_endpoint': ['https://ia.squeep.com/token'],
383 'canonical': ['https://thuza.ratfeathers.com/'],
384 'author': ['https://thuza.ratfeathers.com/'],
385 'me': ['https://thuza.ratfeathers.com/'],
386 'self': ['https://thuza.ratfeathers.com/'],
387 'hub': ['https://hub.squeep.com/'],
388 'preload': ['https://thuza.ratfeathers.com/image.png'],
391 'https://hub.squeep.com/': {
395 'https://ia.squeep.com/auth': {
396 rels: ['authorization_endpoint'],
399 'https://ia.squeep.com/token': {
400 rels: ['token_endpoint'],
403 'https://thuza.ratfeathers.com/': {
404 rels: ['self', 'canonical', 'author', 'me'],
407 'https://thuza.ratfeathers.com/image.png': {
415 photo: ['https://thuza.ratfeathers.com/image.png'],
416 url: ['https://thuza.ratfeathers.com/'],
422 result
= await communication
.fetchMicroformat(urlObj
);
423 assert
.deepStrictEqual(result
, expected
);
425 it('covers axios error', async
function () {
426 communication
.axios
.rejects(new Error('blah'));
427 expected
= undefined;
429 result
= await communication
.fetchMicroformat(urlObj
);
431 assert
.deepStrictEqual(result
, expected
);
433 it('covers non-parsable content', async
function () {
434 response
.data
= 'some bare text';
435 response
.headers
= {};
436 communication
.axios
.resolves(response
);
443 result
= await communication
.fetchMicroformat(urlObj
);
445 assert
.deepStrictEqual(result
, expected
);
447 it('covers non-utf8 content', async
function () {
448 response
.headers
['content-type'] = 'text/html; charset=ASCII';
449 communication
.axios
.resolves(response
);
452 'authorization_endpoint': ['https://ia.squeep.com/auth'],
453 'token_endpoint': ['https://ia.squeep.com/token'],
454 'canonical': ['https://thuza.ratfeathers.com/'],
455 'author': ['https://thuza.ratfeathers.com/'],
456 'me': ['https://thuza.ratfeathers.com/'],
457 'self': ['https://thuza.ratfeathers.com/'],
458 'hub': ['https://hub.squeep.com/'],
459 'preload': ['https://thuza.ratfeathers.com/image.png'],
462 'https://hub.squeep.com/': {
466 'https://ia.squeep.com/auth': {
467 rels: ['authorization_endpoint'],
470 'https://ia.squeep.com/token': {
471 rels: ['token_endpoint'],
474 'https://thuza.ratfeathers.com/': {
475 rels: ['self', 'canonical', 'author', 'me'],
478 'https://thuza.ratfeathers.com/image.png': {
486 photo: ['https://thuza.ratfeathers.com/image.png'],
487 url: ['https://thuza.ratfeathers.com/'],
493 result
= await communication
.fetchMicroformat(urlObj
);
495 assert
.deepStrictEqual(result
, expected
);
497 }); // fetchMicroformat
499 describe('fetchJSON', function () {
500 let expected
, response
, result
, urlObj
;
501 beforeEach(function () {
502 expected
= undefined;
504 urlObj
= new URL('https://thuza.ratfeathers.com/');
506 headers: Object
.assign({}, testData
.linkHeaders
),
507 data: testData
.hCardHtml
,
510 it('covers', async
function () {
511 communication
.axios
.resolves(response
);
512 expected
= { foo: 'bar', baz: 123 };
513 response
.data
= JSON
.stringify(expected
);
515 result
= await communication
.fetchJSON(urlObj
);
516 assert
.deepStrictEqual(result
, expected
);
518 it('covers axios error', async
function () {
519 communication
.axios
.rejects(new Error('blah'));
520 expected
= undefined;
522 result
= await communication
.fetchJSON(urlObj
);
524 assert
.deepStrictEqual(result
, expected
);
526 it('covers non-parsable content', async
function () {
527 response
.data
= 'some bare text';
528 response
.headers
= {};
529 communication
.axios
.resolves(response
);
530 expected
= undefined;
532 result
= await communication
.fetchJSON(urlObj
);
534 assert
.deepStrictEqual(result
, expected
);
538 describe('validateProfile', function () {
539 let url
, validationOptions
;
540 beforeEach(function () {
541 url
= 'https://example.com/';
543 sinon
.stub(dns
, 'lookupAsync').resolves([{ family: 4, address: '10.11.12.14' }]);
545 it('rejects invalid url', async
function () {
547 await assert
.rejects(() => communication
.validateProfile(url
, validationOptions
), ValidationError
);
549 it('covers success', async
function () {
550 const result
= await communication
.validateProfile(url
, validationOptions
);
551 assert
.strictEqual(result
.isLoopback
, false);
553 it('rejects invalid', async
function () {
554 url
= 'ftp://example.com/';
555 await assert
.rejects(() => communication
.validateProfile(url
, validationOptions
), ValidationError
);
558 }); // validateProfile
560 describe('validateClientIdentifier', function () {
561 let url
, validationOptions
;
562 beforeEach(function () {
563 url
= 'https://example.com/';
565 sinon
.stub(dns
, 'lookupAsync').resolves([{ family: 4, address: '10.11.12.13' }]);
567 it('rejects invalid url', async
function () {
569 await communication
.validateClientIdentifier('bad url');
570 assert
.fail(noExpectedException
);
572 assert(e
instanceof ValidationError
);
575 it('rejects invalid scheme', async
function () {
576 url
= 'ftp://example.com/';
578 await communication
.validateClientIdentifier(url
, validationOptions
);
579 assert
.fail(noExpectedException
);
581 assert(e
instanceof ValidationError
);
584 it('rejects fragment', async
function () {
585 url
= 'https://example.com/#foo';
587 await communication
.validateClientIdentifier(url
, validationOptions
);
588 assert
.fail(noExpectedException
);
590 assert(e
instanceof ValidationError
);
593 it('rejects username', async
function () {
594 url
= 'https://user@example.com/';
596 await communication
.validateClientIdentifier(url
, validationOptions
);
597 assert
.fail(noExpectedException
);
599 assert(e
instanceof ValidationError
);
602 it('rejects password', async
function () {
603 url
= 'https://:foo@example.com/';
605 await communication
.validateClientIdentifier(url
, validationOptions
);
606 assert
.fail(noExpectedException
);
608 assert(e
instanceof ValidationError
);
611 it('rejects relative path', async
function () {
612 url
= 'https://example.com/client/../sneaky';
614 await communication
.validateClientIdentifier(url
, validationOptions
);
615 assert
.fail(noExpectedException
);
617 assert(e
instanceof ValidationError
);
620 it('rejects ipv4', async
function () {
621 url
= 'https://10.11.12.13/';
623 await communication
.validateClientIdentifier(url
, validationOptions
);
624 assert
.fail(noExpectedException
);
626 assert(e
instanceof ValidationError
);
629 it('rejects ipv6', async
function () {
630 url
= 'https://[fd64:defa:00e5:caf4:0dff::ad39]/';
632 await communication
.validateClientIdentifier(url
, validationOptions
);
633 assert
.fail(noExpectedException
);
635 assert(e
instanceof ValidationError
);
638 it('accepts ipv4 loopback', async
function () {
639 url
= 'https://127.0.0.1/';
640 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
641 assert
.strictEqual(result
.isLoopback
, true);
643 it('accepts ipv6 loopback', async
function () {
644 url
= 'https://[::1]/';
645 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
646 assert
.strictEqual(result
.isLoopback
, true);
648 it('accepts resolved ipv4 loopback', async
function () {
649 dns
.lookupAsync
.resolves([{ family: 4, address: '127.0.0.1' }]);
650 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
651 assert
.strictEqual(result
.isLoopback
, true);
653 it('accepts resolved ipv6 loopback', async
function () {
654 dns
.lookupAsync
.resolves([{ family: 6, address: '::1' }]);
655 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
656 assert
.strictEqual(result
.isLoopback
, true);
658 it('covers success', async
function () {
659 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
660 assert
.strictEqual(result
.isLoopback
, false);
662 it('rejects resolution failure', async
function () {
663 dns
.lookupAsync
.rejects(new Error('oh no'));
665 await communication
.validateClientIdentifier(url
, validationOptions
);
666 assert
.fail(noExpectedException
);
668 assert(e
instanceof ValidationError
);
671 it('rejects mismatched resolutions', async
function () {
672 dns
.lookupAsync
.onCall(1).resolves([{ family: 4, address: '10.9.8.7' }]);
674 await communication
.validateClientIdentifier(url
, validationOptions
);
675 assert
.fail(noExpectedException
);
677 assert(e
instanceof ValidationError
);
680 it('ignores unknown dns family', async
function () {
681 dns
.lookupAsync
.resolves([{ family: 5, address: '10.9.8.7' }]);
682 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
683 assert
.strictEqual(result
.isLoopback
, false);
685 it('covers rooted hostname', async
function() {
686 url
= 'https://example.com./';
687 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
688 assert
.strictEqual(result
.isLoopback
, false);
690 it('covers unresolved', async
function () {
691 dns
.lookupAsync
.resolves();
692 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
693 assert
.strictEqual(result
.isLoopback
, false);
695 }); // validateClientIdentifier
697 describe('fetchClientIdentifier', function () {
698 let expected
, response
, result
, urlObj
;
699 beforeEach(function () {
700 expected
= undefined;
702 urlObj
= new URL('https://thuza.ratfeathers.com/');
705 data: testData
.multiMF2Html
,
708 it('covers', async
function () {
709 communication
.axios
.resolves(response
);
713 name: ['Also Some Client'],
714 url: ['https://thuza.ratfeathers.com/'],
719 'author': ['https://thuza.ratfeathers.com/'],
720 'authorization_endpoint': ['https://ia.squeep.com/auth'],
721 'canonical': ['https://thuza.ratfeathers.com/'],
722 'me': ['https://thuza.ratfeathers.com/'],
723 'token_endpoint': ['https://ia.squeep.com/token'],
726 result
= await communication
.fetchClientIdentifier(urlObj
);
727 assert
.deepStrictEqual(result
, expected
);
729 it('covers failed fetch', async
function () {
730 communication
.axios
.rejects();
731 expected
= undefined;
732 result
= await communication
.fetchClientIdentifier(urlObj
);
733 assert
.deepStrictEqual(result
, expected
);
735 it('covers no h-app data', async
function () {
736 response
.data
= testData
.noneMF2Html
;
737 communication
.axios
.resolves(response
);
742 result
= await communication
.fetchClientIdentifier(urlObj
);
743 assert
.deepStrictEqual(result
, expected
);
745 it('covers missing fields', async
function () {
746 sinon
.stub(communication
, 'fetchMicroformat').resolves({});
751 result
= await communication
.fetchClientIdentifier(urlObj
);
752 assert
.deepStrictEqual(result
, expected
);
754 it('covers other missing fields', async
function () {
755 sinon
.stub(communication
, 'fetchMicroformat').resolves({
761 url: ['https://example.com'],
770 result
= await communication
.fetchClientIdentifier(urlObj
);
771 assert
.deepStrictEqual(result
, expected
);
773 it('covers loopback', async
function () {
774 sinon
.spy(communication
, 'fetchMicroformat');
775 urlObj
.isLoopback
= true;
780 result
= await communication
.fetchClientIdentifier(urlObj
);
781 assert
.deepStrictEqual(result
, expected
);
782 assert(communication
.fetchMicroformat
.notCalled
);
784 }); // fetchClientIdentifier
786 describe('fetchProfile', function () {
787 let expected
, response
, result
, urlObj
;
788 beforeEach(function () {
789 expected
= undefined;
791 urlObj
= new URL('https://thuza.ratfeathers.com/');
794 data: testData
.hCardHtml
,
796 sinon
.stub(communication
, 'fetchJSON');
798 describe('legacy without indieauth-metadata', function () {
799 it('covers', async
function () {
800 communication
.axios
.resolves(response
);
803 photo: 'https://thuza.ratfeathers.com/image.png',
804 url: 'https://thuza.ratfeathers.com/',
806 authorizationEndpoint: 'https://ia.squeep.com/auth',
807 tokenEndpoint: 'https://ia.squeep.com/token',
809 authorizationEndpoint: 'https://ia.squeep.com/auth',
810 tokenEndpoint: 'https://ia.squeep.com/token',
813 result
= await communication
.fetchProfile(urlObj
);
814 assert
.deepStrictEqual(result
, expected
);
816 it('covers multiple hCards', async
function () {
817 response
.data
= testData
.multiMF2Html
;
818 communication
.axios
.resolves(response
);
822 photo: 'https://thuza.ratfeathers.com/image.png',
823 url: 'https://thuza.ratfeathers.com/',
824 authorizationEndpoint: 'https://ia.squeep.com/auth',
825 tokenEndpoint: 'https://ia.squeep.com/token',
827 authorizationEndpoint: 'https://ia.squeep.com/auth',
828 tokenEndpoint: 'https://ia.squeep.com/token',
831 result
= await communication
.fetchProfile(urlObj
);
832 assert
.deepStrictEqual(result
, expected
);
834 it('covers failed fetch', async
function () {
835 communication
.axios
.rejects();
843 result
= await communication
.fetchProfile(urlObj
);
844 assert
.deepStrictEqual(result
, expected
);
847 it('covers', async
function () {
848 response
.data
= testData
.hCardMetadataHtml
;
849 communication
.axios
.resolves(response
);
850 communication
.fetchJSON
.resolves({
851 'issuer': 'https://ia.squeep.com/',
852 'authorization_endpoint': 'https://ia.squeep.com/auth',
853 'token_endpoint': 'https://ia.squeep.com/token',
854 'introspection_endpoint': 'https://ia.squeep.com/introspect',
855 'introspection_endpoint_auth_methods_supported': [ '' ],
856 'revocation_endpoint': 'https://ia.squeep.com/revoke',
857 'revocation_endpoint_auth_methods_supported': [ 'none' ],
858 'scopes_supported': [ 'profile', 'email' ],
859 'service_documentation': 'https://indieauth.spec.indieweb.org/',
860 'code_challenge_methods_supported': [ 'S256', 'SHA256' ],
861 'authorization_response_iss_parameter_supported': true,
862 'userinfo_endpoint': 'https://ia.squeep.com/userinfo',
866 photo: 'https://thuza.ratfeathers.com/image.png',
867 url: 'https://thuza.ratfeathers.com/',
870 authorizationEndpoint: 'https://ia.squeep.com/auth',
871 tokenEndpoint: 'https://ia.squeep.com/token',
872 issuer: 'https://ia.squeep.com/',
873 introspectionEndpoint: 'https://ia.squeep.com/introspect',
874 introspectionEndpointAuthMethodsSupported: [ '' ],
875 revocationEndpoint: 'https://ia.squeep.com/revoke',
876 revocationEndpointAuthMethodsSupported: [ 'none' ],
877 scopesSupported: [ 'profile', 'email' ],
878 serviceDocumentation: 'https://indieauth.spec.indieweb.org/',
879 codeChallengeMethodsSupported: [ 'S256', 'SHA256' ],
880 authorizationResponseIssParameterSupported: true,
881 userinfoEndpoint: 'https://ia.squeep.com/userinfo',
883 authorizationEndpoint: 'https://ia.squeep.com/auth',
884 tokenEndpoint: 'https://ia.squeep.com/token',
885 indieauthMetadata: 'https://ia.squeep.com/meta',
888 result
= await communication
.fetchProfile(urlObj
);
890 assert
.deepStrictEqual(result
, expected
);
892 it('covers metadata missing fields', async
function () {
893 response
.data
= testData
.hCardMetadataHtml
;
894 communication
.axios
.resolves(response
);
895 communication
.fetchJSON
.resolves({
896 'issuer': 'https://ia.squeep.com/',
900 photo: 'https://thuza.ratfeathers.com/image.png',
901 url: 'https://thuza.ratfeathers.com/',
904 issuer: 'https://ia.squeep.com/',
906 indieauthMetadata: 'https://ia.squeep.com/meta',
909 result
= await communication
.fetchProfile(urlObj
);
911 assert
.deepStrictEqual(result
, expected
);
913 it('covers metadata response failure', async
function () {
914 const jsonError
= new Error('oh no');
915 response
.data
= testData
.hCardMetadataHtml
;
917 .onCall(0).resolves(response
)
918 .onCall(1).rejects(jsonError
);
919 communication
.fetchJSON
.restore();
922 photo: 'https://thuza.ratfeathers.com/image.png',
923 url: 'https://thuza.ratfeathers.com/',
926 indieauthMetadata: 'https://ia.squeep.com/meta',
929 result
= await communication
.fetchProfile(urlObj
);
931 assert
.deepStrictEqual(result
, expected
);
935 describe('redeemProfileCode', function () {
936 let expected
, urlObj
, code
, codeVerifier
, clientId
, redirectURI
;
937 this.beforeEach(function () {
938 urlObj
= new URL('https://example.com/auth');
939 code
= Buffer
.allocUnsafe(42).toString('base64').replace('/', '_').replace('+', '-');
940 codeVerifier
= Buffer
.allocUnsafe(42).toString('base64').replace('/', '_').replace('+', '-');
941 clientId
= 'https://example.com/';
942 redirectURI
= 'https://example.com/_ia';
944 it('covers', async
function () {
945 communication
.axios
.resolves({
946 data: '{"me":"https://profile.example.com/"}',
949 me: 'https://profile.example.com/',
952 const result
= await communication
.redeemProfileCode(urlObj
, code
, codeVerifier
, clientId
, redirectURI
);
954 assert
.deepStrictEqual(result
, expected
);
956 it('covers failure', async
function () {
957 communication
.axios
.resolves('Not a JSON payload.');
959 const result
= await communication
.redeemProfileCode(urlObj
, code
, codeVerifier
, clientId
, redirectURI
);
961 assert
.strictEqual(result
, undefined);