IndieAuth login support, allows viewing of topics related to profile
[websub-hub] / test / src / session-manager.js
1 /* eslint-env mocha */
2 /* eslint-disable capitalized-comments, sonarjs/no-duplicate-string, sonarjs/no-identical-functions */
3
4 'use strict';
5
6 const assert = require('assert');
7 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
8
9 const SessionManager = require('../../src/session-manager');
10 const Config = require('../../config');
11 const stubLogger = require('../stub-logger');
12
13 describe('SessionManager', function () {
14 let manager, options, stubAuthenticator;
15 let res, ctx;
16
17 beforeEach(function () {
18 options = new Config('test');
19 res = {
20 end: sinon.stub(),
21 setHeader: sinon.stub(),
22 };
23 ctx = {
24 cookie: '',
25 params: {},
26 queryParams: {},
27 parsedBody: {},
28 };
29 stubAuthenticator = {
30 isValidIdentifierCredential: sinon.stub(),
31 };
32 manager = new SessionManager(stubLogger, stubAuthenticator, options);
33 sinon.stub(manager.indieAuthCommunication);
34 stubLogger._reset();
35 });
36 afterEach(function () {
37 sinon.restore();
38 });
39
40 describe('_sessionCookieSet', function () {
41 let session, maxAge;
42 beforeEach(function () {
43 session = {};
44 maxAge = 86400;
45 });
46 it('covers', async function () {
47 await manager._sessionCookieSet(res, session, maxAge);
48 assert(res.setHeader.called);
49 });
50 it('covers reset', async function () {
51 session = undefined;
52 maxAge = 0;
53 await manager._sessionCookieSet(res, session, maxAge);
54 assert(res.setHeader.called);
55 });
56 }); // _sessionCookieSet
57
58 describe('getAdminLogin', function () {
59 it('covers', async function () {
60 await manager.getAdminLogin(res, ctx);
61 });
62 }); // getAdminLogin
63
64 describe('postAdminLogin', function () {
65 it('covers valid local', async function () {
66 ctx.parsedBody.identifier = 'user';
67 ctx.parsedBody.credential = 'password';
68 manager.authenticator.isValidIdentifierCredential.resolves(true);
69 await manager.postAdminLogin(res, ctx);
70 assert.strictEqual(res.statusCode, 302);
71 });
72 it('covers invalid local', async function () {
73 ctx.parsedBody.identifier = 'user';
74 ctx.parsedBody.credential = 'password';
75 manager.authenticator.isValidIdentifierCredential.resolves(false);
76 await manager.postAdminLogin(res, ctx);
77 assert(!res.setHeader.called);
78 });
79 it('covers valid profile', async function () {
80 ctx.parsedBody.me = 'https://example.com/profile';
81 manager.indieAuthCommunication.fetchProfile.resolves({
82 authorizationEndpoint: 'https://example.com/auth',
83 });
84 await manager.postAdminLogin(res, ctx);
85 assert.strictEqual(res.statusCode, 302);
86 });
87 it('covers invalid profile', async function () {
88 ctx.parsedBody.me = 'not a profile';
89 manager.indieAuthCommunication.fetchProfile.resolves();
90 await manager.postAdminLogin(res, ctx);
91 assert(!res.setHeader.called);
92 });
93 it('covers invalid profile response', async function () {
94 ctx.parsedBody.me = 'https://example.com/profile';
95 manager.indieAuthCommunication.fetchProfile.resolves();
96 await manager.postAdminLogin(res, ctx);
97 assert(!res.setHeader.called);
98 });
99 it('covers invalid profile response endpoint', async function () {
100 ctx.parsedBody.me = 'https://example.com/profile';
101 manager.indieAuthCommunication.fetchProfile.resolves({
102 authorizationEndpoint: 'not an auth endpoint',
103 });
104 await manager.postAdminLogin(res, ctx);
105 assert(!res.setHeader.called);
106 });
107 }); // postAdminLogin
108
109 describe('getAdminLogout', function () {
110 it('covers', async function () {
111 await manager.getAdminLogout(res, ctx);
112 });
113 }); // getAdminLogout
114
115 describe('getAdminIA', function () {
116 let state, me, authorizationEndpoint;
117 beforeEach(function () {
118 state = '4ea7e936-3427-11ec-9f4b-0025905f714a';
119 me = 'https://example.com/profile';
120 authorizationEndpoint = 'https://example.com/auth'
121 ctx.cookie = 'WSHas=sessionCookie';
122 manager.indieAuthCommunication.redeemProfileCode.resolves({
123 me,
124 });
125 manager.indieAuthCommunication.fetchProfile.resolves({
126 authorizationEndpoint,
127 });
128 sinon.stub(manager.mysteryBox, 'unpack').resolves({
129 authorizationEndpoint,
130 state,
131 me,
132 });
133 });
134 it('covers valid', async function () {
135 ctx.queryParams['state'] = state;
136 ctx.queryParams['code'] = 'codeCodeCode';
137
138 await manager.getAdminIA(res, ctx);
139
140 assert.strictEqual(res.statusCode, 302);
141 });
142 it('covers missing cookie', async function () {
143 delete ctx.cookie;
144
145 await manager.getAdminIA(res, ctx);
146
147 assert(ctx.errors.length);
148 });
149 it('covers invalid cookie', async function () {
150 manager.mysteryBox.unpack.restore();
151 sinon.stub(manager.mysteryBox, 'unpack').rejects();
152
153 await manager.getAdminIA(res, ctx);
154
155 assert(ctx.errors.length);
156 });
157 it('covers mis-matched state', async function () {
158 ctx.queryParams['state'] = 'incorrect-state';
159 ctx.queryParams['code'] = 'codeCodeCode';
160
161 await manager.getAdminIA(res, ctx);
162
163 assert(ctx.errors.length);
164 });
165 it('relays auth endpoint errors', async function () {
166 ctx.queryParams['state'] = state;
167 ctx.queryParams['code'] = 'codeCodeCode';
168 ctx.queryParams['error'] = 'error_code';
169 ctx.queryParams['error_description'] = 'something went wrong';
170
171 await manager.getAdminIA(res, ctx);
172
173 assert(ctx.errors.length);
174 });
175 it('covers invalid restored session', async function () {
176 manager.mysteryBox.unpack.restore();
177 sinon.stub(manager.mysteryBox, 'unpack').resolves({
178 authorizationEndpoint: 'not a url',
179 state,
180 me,
181 });
182 ctx.queryParams['state'] = state;
183 ctx.queryParams['code'] = 'codeCodeCode';
184
185 await manager.getAdminIA(res, ctx);
186
187 assert(ctx.errors.length);
188 });
189 it('covers empty profile redemption response', async function () {
190 ctx.queryParams['state'] = state;
191 ctx.queryParams['code'] = 'codeCodeCode';
192 manager.indieAuthCommunication.redeemProfileCode.restore();
193 sinon.stub(manager.indieAuthCommunication, 'redeemProfileCode').resolves();
194
195 await manager.getAdminIA(res, ctx);
196
197 assert(ctx.errors.length);
198 });
199 it('covers missing profile in redemption response', async function () {
200 ctx.queryParams['state'] = state;
201 ctx.queryParams['code'] = 'codeCodeCode';
202 manager.indieAuthCommunication.redeemProfileCode.restore();
203 sinon.stub(manager.indieAuthCommunication, 'redeemProfileCode').resolves({
204 });
205
206 await manager.getAdminIA(res, ctx);
207
208 assert(ctx.errors.length);
209 });
210 it('covers different canonical profile response', async function () {
211 ctx.queryParams['state'] = state;
212 ctx.queryParams['code'] = 'codeCodeCode';
213 manager.indieAuthCommunication.redeemProfileCode.restore();
214 sinon.stub(manager.indieAuthCommunication, 'redeemProfileCode').resolves({
215 me: 'https://different.example.com/profile',
216 });
217
218 await manager.getAdminIA(res, ctx);
219
220 assert.strictEqual(res.statusCode, 302);
221 });
222 it('covers different canonical profile response mis-matched endpoint', async function () {
223 ctx.queryParams['state'] = state;
224 ctx.queryParams['code'] = 'codeCodeCode';
225 manager.indieAuthCommunication.redeemProfileCode.restore();
226 sinon.stub(manager.indieAuthCommunication, 'redeemProfileCode').resolves({
227 me: 'https://different.example.com/profile',
228 });
229 manager.indieAuthCommunication.fetchProfile.restore();
230 sinon.stub(manager.indieAuthCommunication, 'fetchProfile').resolves({
231 authorizationEndpoint: 'https://elsewhere.example.com/auth',
232 });
233
234 await manager.getAdminIA(res, ctx);
235
236 assert(ctx.errors.length);
237 });
238 }); // getAdminIA
239
240 }); // SessionManager