2 /* eslint-disable capitalized-comments, sonarjs/no-duplicate-string, sonarjs/no-identical-functions */
6 const assert
= require('assert');
7 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
9 const SessionManager
= require('../../lib/session-manager');
10 const Config
= require('../stub-config');
11 const stubLogger
= require('../stub-logger');
13 describe('SessionManager', function () {
14 let manager
, options
, stubAuthenticator
;
17 beforeEach(function () {
18 options
= new Config('test');
21 setHeader: sinon
.stub(),
30 isValidIdentifierCredential: sinon
.stub(),
32 manager
= new SessionManager(stubLogger
, stubAuthenticator
, options
);
33 sinon
.stub(manager
.indieAuthCommunication
);
36 afterEach(function () {
40 describe('_sessionCookieSet', function () {
42 beforeEach(function () {
46 it('covers', async
function () {
47 await manager
._sessionCookieSet(res
, session
, maxAge
);
48 assert(res
.setHeader
.called
);
50 it('covers reset', async
function () {
53 await manager
._sessionCookieSet(res
, session
, maxAge
);
54 assert(res
.setHeader
.called
);
56 it('covers options', async
function() {
57 options
.authenticator
.secureAuthOnly
= false;
58 await manager
._sessionCookieSet(res
, session
, undefined, '');
59 assert(res
.setHeader
.called
);
61 }); // _sessionCookieSet
63 describe('getAdminLogin', function () {
64 it('covers', async
function () {
65 await manager
.getAdminLogin(res
, ctx
);
69 describe('postAdminLogin', function () {
70 it('covers valid local', async
function () {
71 ctx
.parsedBody
.identifier
= 'user';
72 ctx
.parsedBody
.credential
= 'password';
73 manager
.authenticator
.isValidIdentifierCredential
.resolves(true);
74 await manager
.postAdminLogin(res
, ctx
);
75 assert
.strictEqual(res
.statusCode
, 302);
77 it('covers invalid local', async
function () {
78 ctx
.parsedBody
.identifier
= 'user';
79 ctx
.parsedBody
.credential
= 'password';
80 manager
.authenticator
.isValidIdentifierCredential
.resolves(false);
81 await manager
.postAdminLogin(res
, ctx
);
82 assert(!res
.setHeader
.called
);
84 it('covers valid profile', async
function () {
85 ctx
.parsedBody
.me
= 'https://example.com/profile';
86 manager
.indieAuthCommunication
.fetchProfile
.resolves({
88 authorizationEndpoint: 'https://example.com/auth',
91 await manager
.postAdminLogin(res
, ctx
);
92 assert
.strictEqual(res
.statusCode
, 302);
94 it('covers invalid profile', async
function () {
95 ctx
.parsedBody
.me
= 'not a profile';
96 manager
.indieAuthCommunication
.fetchProfile
.resolves();
97 await manager
.postAdminLogin(res
, ctx
);
98 assert(!res
.setHeader
.called
);
100 it('covers invalid profile response', async
function () {
101 ctx
.parsedBody
.me
= 'https://example.com/profile';
102 manager
.indieAuthCommunication
.fetchProfile
.resolves();
103 await manager
.postAdminLogin(res
, ctx
);
104 assert(!res
.setHeader
.called
);
106 it('covers invalid profile response endpoint', async
function () {
107 ctx
.parsedBody
.me
= 'https://example.com/profile';
108 manager
.indieAuthCommunication
.fetchProfile
.resolves({
110 authorizationEndpoint: 'not an auth endpoint',
113 await manager
.postAdminLogin(res
, ctx
);
114 assert(!res
.setHeader
.called
);
116 it('covers profile scheme fallback', async
function () {
117 ctx
.parsedBody
.me
= 'https://example.com/profile';
118 ctx
.parsedBody
.me_auto_scheme
= '1';
119 manager
.indieAuthCommunication
.fetchProfile
120 .onCall(0).resolves()
121 .onCall(1).resolves({
123 issuer: 'https://example.com/',
124 authorizationEndpoint: 'https://example.com/auth',
127 await manager
.postAdminLogin(res
, ctx
);
128 assert
.strictEqual(res
.statusCode
, 302);
131 describe('living-standard-20220212', function () {
132 it('covers valid profile', async
function () {
133 ctx
.parsedBody
.me
= 'https://example.com/profile';
134 manager
.indieAuthCommunication
.fetchProfile
.resolves({
136 issuer: 'https://example.com/',
137 authorizationEndpoint: 'https://example.com/auth',
140 await manager
.postAdminLogin(res
, ctx
);
141 assert
.strictEqual(res
.statusCode
, 302);
143 it('covers bad issuer url', async
function () {
144 ctx
.parsedBody
.me
= 'https://example.com/profile';
145 manager
.indieAuthCommunication
.fetchProfile
.resolves({
147 issuer: 'http://example.com/?bah#foo',
148 authorizationEndpoint: 'https://example.com/auth',
151 await manager
.postAdminLogin(res
, ctx
);
152 assert(!res
.setHeader
.called
);
154 it('covers unparsable issuer url', async
function () {
155 ctx
.parsedBody
.me
= 'https://example.com/profile';
156 manager
.indieAuthCommunication
.fetchProfile
.resolves({
159 authorizationEndpoint: 'https://example.com/auth',
162 await manager
.postAdminLogin(res
, ctx
);
163 assert(!res
.setHeader
.called
);
165 }); // living-standard-20220212
166 }); // postAdminLogin
168 describe('getAdminLogout', function () {
169 it('covers', async
function () {
170 await manager
.getAdminLogout(res
, ctx
);
172 }); // getAdminLogout
174 describe('getAdminIA', function () {
175 let state
, me
, authorizationEndpoint
;
176 beforeEach(function () {
177 state
= '4ea7e936-3427-11ec-9f4b-0025905f714a';
178 me
= 'https://example.com/profile';
179 authorizationEndpoint
= 'https://example.com/auth'
180 ctx
.cookie
= 'squeepSession=sessionCookie';
181 manager
.indieAuthCommunication
.redeemProfileCode
.resolves({
184 manager
.indieAuthCommunication
.fetchProfile
.resolves({
186 authorizationEndpoint
,
189 sinon
.stub(manager
.mysteryBox
, 'unpack').resolves({
190 authorizationEndpoint
,
195 it('covers valid', async
function () {
196 ctx
.queryParams
['state'] = state
;
197 ctx
.queryParams
['code'] = 'codeCodeCode';
199 await manager
.getAdminIA(res
, ctx
);
201 assert
.strictEqual(res
.statusCode
, 302);
203 it('covers missing cookie', async
function () {
206 await manager
.getAdminIA(res
, ctx
);
208 assert(ctx
.errors
.length
);
210 it('covers invalid cookie', async
function () {
211 manager
.mysteryBox
.unpack
.restore();
212 sinon
.stub(manager
.mysteryBox
, 'unpack').rejects();
214 await manager
.getAdminIA(res
, ctx
);
216 assert(ctx
.errors
.length
);
218 it('covers mis-matched state', async
function () {
219 ctx
.queryParams
['state'] = 'incorrect-state';
220 ctx
.queryParams
['code'] = 'codeCodeCode';
222 await manager
.getAdminIA(res
, ctx
);
224 assert(ctx
.errors
.length
);
226 it('relays auth endpoint errors', async
function () {
227 ctx
.queryParams
['state'] = state
;
228 ctx
.queryParams
['code'] = 'codeCodeCode';
229 ctx
.queryParams
['error'] = 'error_code';
230 ctx
.queryParams
['error_description'] = 'something went wrong';
232 await manager
.getAdminIA(res
, ctx
);
234 assert(ctx
.errors
.length
);
236 it('covers empty error_description', async
function () {
237 ctx
.queryParams
['state'] = state
;
238 ctx
.queryParams
['code'] = 'codeCodeCode';
239 ctx
.queryParams
['error'] = 'error_code';
241 await manager
.getAdminIA(res
, ctx
);
243 assert(ctx
.errors
.length
);
245 it('covers invalid restored session', async
function () {
246 manager
.mysteryBox
.unpack
.restore();
247 sinon
.stub(manager
.mysteryBox
, 'unpack').resolves({
248 authorizationEndpoint: 'not a url',
252 ctx
.queryParams
['state'] = state
;
253 ctx
.queryParams
['code'] = 'codeCodeCode';
255 await manager
.getAdminIA(res
, ctx
);
257 assert(ctx
.errors
.length
);
259 it('covers empty profile redemption response', async
function () {
260 ctx
.queryParams
['state'] = state
;
261 ctx
.queryParams
['code'] = 'codeCodeCode';
262 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
263 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves();
265 await manager
.getAdminIA(res
, ctx
);
267 assert(ctx
.errors
.length
);
269 it('covers missing profile in redemption response', async
function () {
270 ctx
.queryParams
['state'] = state
;
271 ctx
.queryParams
['code'] = 'codeCodeCode';
272 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
273 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves({
276 await manager
.getAdminIA(res
, ctx
);
278 assert(ctx
.errors
.length
);
280 it('covers different canonical profile response', async
function () {
281 ctx
.queryParams
['state'] = state
;
282 ctx
.queryParams
['code'] = 'codeCodeCode';
283 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
284 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves({
285 me: 'https://different.example.com/profile',
288 await manager
.getAdminIA(res
, ctx
);
290 assert
.strictEqual(res
.statusCode
, 302);
292 it('covers different canonical profile response mis-matched endpoint', async
function () {
293 ctx
.queryParams
['state'] = state
;
294 ctx
.queryParams
['code'] = 'codeCodeCode';
295 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
296 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves({
297 me: 'https://different.example.com/profile',
299 manager
.indieAuthCommunication
.fetchProfile
.restore();
300 sinon
.stub(manager
.indieAuthCommunication
, 'fetchProfile').resolves({
302 authorizationEndpoint: 'https://elsewhere.example.com/auth',
306 await manager
.getAdminIA(res
, ctx
);
308 assert(ctx
.errors
.length
);
310 describe('living-standard-20220212', function () {
311 beforeEach(function () {
312 manager
.indieAuthCommunication
.fetchProfile
.resolves({
314 authorizationEndpoint
,
315 issuer: 'https://example.com/',
318 manager
.mysteryBox
.unpack
.resolves({
319 authorizationEndpoint
,
320 issuer: 'https://example.com/',
325 it('covers valid', async
function () {
326 ctx
.queryParams
['state'] = state
;
327 ctx
.queryParams
['code'] = 'codeCodeCode';
328 ctx
.queryParams
['iss'] = 'https://example.com/';
330 await manager
.getAdminIA(res
, ctx
);
332 assert
.strictEqual(res
.statusCode
, 302);
334 it('covers mis-matched issuer', async
function () {
335 ctx
.queryParams
['state'] = state
;
336 ctx
.queryParams
['code'] = 'codeCodeCode';
338 await manager
.getAdminIA(res
, ctx
);
340 assert(ctx
.errors
.length
);
342 }); // living-standard-20220212
345 }); // SessionManager