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 describe('living-standard-20220212', function () {
117 it('covers valid profile', async
function () {
118 ctx
.parsedBody
.me
= 'https://example.com/profile';
119 manager
.indieAuthCommunication
.fetchProfile
.resolves({
121 issuer: 'https://example.com/',
122 authorizationEndpoint: 'https://example.com/auth',
125 await manager
.postAdminLogin(res
, ctx
);
126 assert
.strictEqual(res
.statusCode
, 302);
128 it('covers bad issuer url', async
function () {
129 ctx
.parsedBody
.me
= 'https://example.com/profile';
130 manager
.indieAuthCommunication
.fetchProfile
.resolves({
132 issuer: 'http://example.com/?bah#foo',
133 authorizationEndpoint: 'https://example.com/auth',
136 await manager
.postAdminLogin(res
, ctx
);
137 assert(!res
.setHeader
.called
);
139 it('covers unparsable issuer url', async
function () {
140 ctx
.parsedBody
.me
= 'https://example.com/profile';
141 manager
.indieAuthCommunication
.fetchProfile
.resolves({
144 authorizationEndpoint: 'https://example.com/auth',
147 await manager
.postAdminLogin(res
, ctx
);
148 assert(!res
.setHeader
.called
);
150 }); // living-standard-20220212
151 }); // postAdminLogin
153 describe('getAdminLogout', function () {
154 it('covers', async
function () {
155 await manager
.getAdminLogout(res
, ctx
);
157 }); // getAdminLogout
159 describe('getAdminIA', function () {
160 let state
, me
, authorizationEndpoint
;
161 beforeEach(function () {
162 state
= '4ea7e936-3427-11ec-9f4b-0025905f714a';
163 me
= 'https://example.com/profile';
164 authorizationEndpoint
= 'https://example.com/auth'
165 ctx
.cookie
= 'squeepSession=sessionCookie';
166 manager
.indieAuthCommunication
.redeemProfileCode
.resolves({
169 manager
.indieAuthCommunication
.fetchProfile
.resolves({
171 authorizationEndpoint
,
174 sinon
.stub(manager
.mysteryBox
, 'unpack').resolves({
175 authorizationEndpoint
,
180 it('covers valid', async
function () {
181 ctx
.queryParams
['state'] = state
;
182 ctx
.queryParams
['code'] = 'codeCodeCode';
184 await manager
.getAdminIA(res
, ctx
);
186 assert
.strictEqual(res
.statusCode
, 302);
188 it('covers missing cookie', async
function () {
191 await manager
.getAdminIA(res
, ctx
);
193 assert(ctx
.errors
.length
);
195 it('covers invalid cookie', async
function () {
196 manager
.mysteryBox
.unpack
.restore();
197 sinon
.stub(manager
.mysteryBox
, 'unpack').rejects();
199 await manager
.getAdminIA(res
, ctx
);
201 assert(ctx
.errors
.length
);
203 it('covers mis-matched state', async
function () {
204 ctx
.queryParams
['state'] = 'incorrect-state';
205 ctx
.queryParams
['code'] = 'codeCodeCode';
207 await manager
.getAdminIA(res
, ctx
);
209 assert(ctx
.errors
.length
);
211 it('relays auth endpoint errors', async
function () {
212 ctx
.queryParams
['state'] = state
;
213 ctx
.queryParams
['code'] = 'codeCodeCode';
214 ctx
.queryParams
['error'] = 'error_code';
215 ctx
.queryParams
['error_description'] = 'something went wrong';
217 await manager
.getAdminIA(res
, ctx
);
219 assert(ctx
.errors
.length
);
221 it('covers empty error_description', async
function () {
222 ctx
.queryParams
['state'] = state
;
223 ctx
.queryParams
['code'] = 'codeCodeCode';
224 ctx
.queryParams
['error'] = 'error_code';
226 await manager
.getAdminIA(res
, ctx
);
228 assert(ctx
.errors
.length
);
230 it('covers invalid restored session', async
function () {
231 manager
.mysteryBox
.unpack
.restore();
232 sinon
.stub(manager
.mysteryBox
, 'unpack').resolves({
233 authorizationEndpoint: 'not a url',
237 ctx
.queryParams
['state'] = state
;
238 ctx
.queryParams
['code'] = 'codeCodeCode';
240 await manager
.getAdminIA(res
, ctx
);
242 assert(ctx
.errors
.length
);
244 it('covers empty profile redemption response', async
function () {
245 ctx
.queryParams
['state'] = state
;
246 ctx
.queryParams
['code'] = 'codeCodeCode';
247 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
248 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves();
250 await manager
.getAdminIA(res
, ctx
);
252 assert(ctx
.errors
.length
);
254 it('covers missing profile in redemption response', async
function () {
255 ctx
.queryParams
['state'] = state
;
256 ctx
.queryParams
['code'] = 'codeCodeCode';
257 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
258 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves({
261 await manager
.getAdminIA(res
, ctx
);
263 assert(ctx
.errors
.length
);
265 it('covers different canonical profile response', async
function () {
266 ctx
.queryParams
['state'] = state
;
267 ctx
.queryParams
['code'] = 'codeCodeCode';
268 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
269 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves({
270 me: 'https://different.example.com/profile',
273 await manager
.getAdminIA(res
, ctx
);
275 assert
.strictEqual(res
.statusCode
, 302);
277 it('covers different canonical profile response mis-matched endpoint', async
function () {
278 ctx
.queryParams
['state'] = state
;
279 ctx
.queryParams
['code'] = 'codeCodeCode';
280 manager
.indieAuthCommunication
.redeemProfileCode
.restore();
281 sinon
.stub(manager
.indieAuthCommunication
, 'redeemProfileCode').resolves({
282 me: 'https://different.example.com/profile',
284 manager
.indieAuthCommunication
.fetchProfile
.restore();
285 sinon
.stub(manager
.indieAuthCommunication
, 'fetchProfile').resolves({
287 authorizationEndpoint: 'https://elsewhere.example.com/auth',
291 await manager
.getAdminIA(res
, ctx
);
293 assert(ctx
.errors
.length
);
295 describe('living-standard-20220212', function () {
296 beforeEach(function () {
297 manager
.indieAuthCommunication
.fetchProfile
.resolves({
299 authorizationEndpoint
,
300 issuer: 'https://example.com/',
303 manager
.mysteryBox
.unpack
.resolves({
304 authorizationEndpoint
,
305 issuer: 'https://example.com/',
310 it('covers valid', async
function () {
311 ctx
.queryParams
['state'] = state
;
312 ctx
.queryParams
['code'] = 'codeCodeCode';
313 ctx
.queryParams
['iss'] = 'https://example.com/';
315 await manager
.getAdminIA(res
, ctx
);
317 assert
.strictEqual(res
.statusCode
, 302);
319 it('covers mis-matched issuer', async
function () {
320 ctx
.queryParams
['state'] = state
;
321 ctx
.queryParams
['code'] = 'codeCodeCode';
323 await manager
.getAdminIA(res
, ctx
);
325 assert(ctx
.errors
.length
);
327 }); // living-standard-20220212
330 }); // SessionManager