• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GEWIS / sudosos-backend / 18698552561

21 Oct 2025 09:45PM UTC coverage: 89.878% (+0.002%) from 89.876%
18698552561

Pull #471

github

web-flow
Merge c13af3de5 into d3814f109
Pull Request #471: Add `authentication` docs

1370 of 1628 branches covered (84.15%)

Branch coverage included in aggregate %.

1 of 2 new or added lines in 1 file covered. (50.0%)

14 existing lines in 3 files now uncovered.

7208 of 7916 relevant lines covered (91.06%)

1111.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.55
/src/controller/authentication-secure-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2024  Study association GEWIS
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as published
7
 *  by the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 *
18
 *  @license
19
 */
20

21
/**
22
 * @module Authentication
23
 */
24

25
import { Response } from 'express';
26
import log4js, { Logger } from 'log4js';
2✔
27
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
28
import Policy from './policy';
29
import { RequestWithToken } from '../middleware/token-middleware';
30
import AuthenticationService from '../service/authentication-service';
2✔
31
import TokenHandler from '../authentication/token-handler';
32
import User from '../entity/user/user';
2✔
33
import PointOfSaleController from './point-of-sale-controller';
2✔
34
import PointOfSale from '../entity/point-of-sale/point-of-sale';
2✔
35
import ServerSettingsStore from '../server-settings/server-settings-store';
2✔
36
import { ISettings } from '../entity/server-setting';
37
import { QRAuthenticatorStatus } from '../entity/authenticator/qr-authenticator';
2✔
38
import WebSocketService from '../service/websocket-service';
2✔
39
import QRService from '../service/qr-service';
2✔
40

41
/**
42
 * The authentication secure controller handles all requests related to authentication that require the user to be authenticated.
43
 *
44
 * Mostly used for refreshing tokens and authenticating Point of Sales.
45
 */
46
export default class AuthenticationSecureController extends BaseController {
2✔
47
  private logger: Logger = log4js.getLogger('AuthenticationController');
2✔
48

49
  /**
50
   * Reference to the token handler of the application.
51
   */
52
  protected tokenHandler: TokenHandler;
53

54
  /**
55
   * Creates a new banner controller instance.
56
   * @param options - The options passed to the base controller.
57
   * @param tokenHandler - The token handler for creating signed tokens.
58
   */
59
  public constructor(options: BaseControllerOptions, tokenHandler: TokenHandler) {
60
    super(options);
2✔
61
    this.logger.level = process.env.LOG_LEVEL;
2✔
62
    this.tokenHandler = tokenHandler;
2✔
63
  }
64

65
  /**
66
   * @inheritdoc
67
   */
68
  public getPolicy(): Policy {
69
    return {
2✔
70
      '/refreshToken': {
71
        GET: {
72
          policy: async () => Promise.resolve(true),
1✔
73
          handler: this.refreshToken.bind(this),
74
          restrictions: { lesser: true, acceptedTOS: false },
75
        },
76
      },
77
      '/pointofsale/:id(\\d+)': {
78
        GET: {
79
          policy: async (req) => this.roleManager.can(req.token.roles, 'authenticate', await PointOfSaleController.getRelation(req), 'User', ['pointOfSale']),
6✔
80
          handler: this.authenticatePointOfSale.bind(this),
81
        },
82
      },
83
      '/qr/:sessionId/confirm': {
84
        POST: {
85
          policy: async () => Promise.resolve(true),
11✔
86
          handler: this.confirmQRCode.bind(this),
87
          restrictions: { lesser: false },
88
        },
89
      },
90
    };
91
  }
92

93
  /**
94
   * GET /authentication/refreshToken
95
   * @summary Get a new JWT token, lesser if the existing token is also lesser
96
   * @operationId refreshToken
97
   * @tags authenticate - Operations of the authentication controller
98
   * @security JWT
99
   * @return {AuthenticationResponse} 200 - The created json web token.
100
   */
101
  private async refreshToken(req: RequestWithToken, res: Response): Promise<void> {
102
    this.logger.trace('Refresh token for user', req.token.user.id);
1✔
103

104
    try {
1✔
105
      const user = await User.findOne({ where: { id: req.token.user.id } });
1✔
106
      const token = await new AuthenticationService().getSaltedToken(user, {
1✔
107
        roleManager: this.roleManager,
108
        tokenHandler: this.tokenHandler,
109
      }, req.token.lesser);
110
      res.json(token);
1✔
111
    } catch (error) {
UNCOV
112
      this.logger.error('Could not create token:', error);
×
UNCOV
113
      res.status(500).json('Internal server error.');
×
114
    }
115
  }
116

117
  /**
118
   * GET /authentication/pointofsale/{id}
119
   * @summary Get a JWT token for the given POS
120
   * @operationId authenticatePointOfSale
121
   * @tags authenticate - Operations of the authentication controller
122
   * @security JWT
123
   * @param {integer} id.path.required - The id of the user
124
   * @return {AuthenticationResponse} 200 - The created json web token.
125
   * @return {string} 404 - Point of sale not found
126
   * @return {string} 500 - Internal server error
127
   */
128
  private async authenticatePointOfSale(req: RequestWithToken, res: Response): Promise<void> {
129
    this.logger.trace('Authenticate point of sale', req.params.id, 'by user', req.token.user.id);
5✔
130

131
    try {
5✔
132
      const pointOfSaleId = Number(req.params.id);
5✔
133
      const pointOfSale = await PointOfSale.findOne({ where: { id: pointOfSaleId }, relations: { user: true } });
5✔
134
      if (!pointOfSale) {
5✔
135
        res.status(404).json('Point of sale not found.');
2✔
136
        return;
2✔
137
      }
138

139
      const expiry = ServerSettingsStore.getInstance().getSetting('jwtExpiryPointOfSale') as ISettings['jwtExpiryPointOfSale'];
3✔
140
      const token = await new AuthenticationService().getSaltedToken(pointOfSale.user, {
3✔
141
        roleManager: this.roleManager,
142
        tokenHandler: this.tokenHandler,
143
      }, false, undefined, expiry);
144
      res.json(token);
3✔
145
    } catch (error) {
UNCOV
146
      this.logger.error('Could not create token:', error);
×
UNCOV
147
      res.status(500).json('Internal server error.');
×
148
    }
149
  }
150

151
  /**
152
   * POST /authentication/qr/{sessionId}/confirm
153
   * @summary Confirm QR code authentication from mobile app
154
   * @operationId confirmQRCode
155
   * @tags authenticate - Operations of authentication controller
156
   * @param {string} sessionId.path.required - The session ID
157
   * @security JWT
158
   * @return 200 - Successfully confirmed
159
   * @return {string} 400 - Validation error
160
   * @return {string} 404 - Session not found
161
   * @return {string} 410 - Session expired
162
   * @return {string} 500 - Internal server error
163
   */
164
  private async confirmQRCode(req: RequestWithToken, res: Response): Promise<void> {
165
    const { sessionId } = req.params;
11✔
166
    this.logger.trace('Confirming QR code for session', sessionId, 'by user', req.token.user);
11✔
167

168
    try {
11✔
169
      const qrAuthenticator = await (new QRService()).get(sessionId);
11✔
170
      if (!qrAuthenticator) {
10✔
171
        res.status(404).json('Session not found.');
1✔
172
        return;
1✔
173
      }
174

175
      if (qrAuthenticator.status === QRAuthenticatorStatus.EXPIRED) {
9✔
176
        res.status(410).json('Session has expired.');
1✔
177
        return;
1✔
178
      }
179

180
      if (qrAuthenticator.status !== QRAuthenticatorStatus.PENDING) {
8✔
181
        res.status(400).json('Session is no longer pending.');
2✔
182
        return;
2✔
183
      }
184

185
      const user = await User.findOne({ where: { id: req.token.user.id } });
6✔
186
      const token = await new AuthenticationService().getSaltedToken(user, {
6✔
187
        roleManager: this.roleManager,
188
        tokenHandler: this.tokenHandler,
189
      }, req.token.lesser);
190

191
      // Let the service handle all business logic validation
192
      await (new QRService()).confirm(qrAuthenticator, user);
5✔
193

194
      // Notify WebSocket clients about the confirmation
195
      WebSocketService.emitQRConfirmed(qrAuthenticator, token);
4✔
196
      res.status(200).json({ message: 'QR code confirmed successfully.' });
3✔
197
    } catch (error) {
198
      this.logger.error('Could not confirm QR code:', error);
4✔
199
      res.status(500).json('Internal server error.');
4✔
200
    }
201
  }
202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc