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

GEWIS / sudosos-backend / 21366511389

26 Jan 2026 05:03PM UTC coverage: 89.574% (-0.03%) from 89.601%
21366511389

Pull #622

github

web-flow
Merge 9d1dfbf35 into b311a076f
Pull Request #622: Add general documentation to the docs

1706 of 2056 branches covered (82.98%)

Branch coverage included in aggregate %.

8810 of 9684 relevant lines covered (90.97%)

1000.87 hits per line

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

85.94
/src/gewis/controller/gewis-authentication-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2026 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
 * This is the module page of the gewis-authentication-controller.
23
 *
24
 * @module GEWIS
25
 */
26

27
import { Request, Response } from 'express';
28
import * as jwt from 'jsonwebtoken';
2✔
29
import log4js, { Logger } from 'log4js';
2✔
30
import * as util from 'util';
2✔
31
import BaseController, { BaseControllerOptions } from '../../controller/base-controller';
2✔
32
import Policy from '../../controller/policy';
33
import TokenHandler from '../../authentication/token-handler';
34
import MemberUser from '../../entity/user/member-user';
2✔
35
import GewiswebToken from '../gewisweb-token';
36
import GewiswebAuthenticationRequest from './request/gewisweb-authentication-request';
37
import AuthenticationService from '../../service/authentication-service';
2✔
38
import GEWISAuthenticationPinRequest from './request/gewis-authentication-pin-request';
39
import AuthenticationLDAPRequest from '../../controller/request/authentication-ldap-request';
40
import AuthenticationController from '../../controller/authentication-controller';
2✔
41
import Gewis from '../gewis';
2✔
42
import UserService from '../../service/user-service';
2✔
43
import { webResponseToUpdate } from '../helpers/gewis-helper';
2✔
44
import { UserType } from '../../entity/user/user';
2✔
45

46
/**
47
  * The GEWIS authentication controller is responsible for:
48
  * - Verifying user authentications.
49
  * - Handing out json web tokens.
50
  */
51
export default class GewisAuthenticationController extends BaseController {
2✔
52
  /**
53
    * Reference to the logger instance.
54
    */
55
  private logger: Logger = log4js.getLogger('GewisAuthenticationController');
2✔
56

57
  /**
58
    * Reference to the token handler of the application.
59
    */
60
  private tokenHandler: TokenHandler;
61

62
  /**
63
   * The secret key shared with gewisweb for JWT HMAC verification.
64
   */
65
  private gewiswebSecret: string;
66

67
  /**
68
    * Creates a new authentication controller instance.
69
    * @param options - The options passed to the base controller.
70
    * @param tokenHandler - The token handler for creating signed tokens.
71
    * @param gewiswebSecret - The shared JWT secret with gewisweb.
72
    */
73
  public constructor(
74
    options: BaseControllerOptions,
75
    tokenHandler: TokenHandler,
76
    gewiswebSecret: string,
77
  ) {
78
    super(options);
2✔
79
    this.logger.level = process.env.LOG_LEVEL;
2✔
80
    this.tokenHandler = tokenHandler;
2✔
81
    this.gewiswebSecret = gewiswebSecret;
2✔
82
  }
83

84
  /**
85
    * @inheritdoc
86
    */
87
  public getPolicy(): Policy {
88
    return {
2✔
89
      '/gewisweb': {
90
        GET: {
91
          policy: async () => true,
1✔
92
          handler: this.getGEWISWebPublic.bind(this),
93
        },
94
        POST: {
95
          body: { modelName: 'GewiswebAuthenticationRequest' },
96
          policy: async () => true,
5✔
97
          handler: this.gewiswebLogin.bind(this),
98
        },
99
      },
100
      '/GEWIS/pin': {
101
        POST: {
102
          body: { modelName: 'GEWISAuthenticationPinRequest' },
103
          policy: async () => true,
3✔
104
          handler: this.gewisPINLogin.bind(this),
105
        },
106
      },
107
      '/GEWIS/LDAP': {
108
        POST: {
109
          body: { modelName: 'AuthenticationLDAPRequest' },
110
          policy: async () => true,
2✔
111
          handler: this.ldapLogin.bind(this),
112
          restrictions: { availableDuringMaintenance: true },
113
        },
114
      },
115
    };
116
  }
117

118
  /**
119
   * GET /authentication/gewisweb
120
   * @summary Get the GEWISWeb public token used by SudoSOS
121
   * @operationId getGEWISWebPublic
122
   * @tags authenticate - Operations of authentication controller
123
   * @returns {string} 200 - Public key
124
   */
125
  public async getGEWISWebPublic(req: Request, res: Response): Promise<void> {
126
    this.logger.trace('Get GEWISWeb public token by IP', req.ip);
1✔
127

128
    res.json(process.env.GEWISWEB_PUBLIC_TOKEN);
1✔
129
  }
130

131
  /**
132
    * POST /authentication/gewisweb
133
    * @summary GEWIS login verification based on gewisweb JWT tokens.
134
    * This method verifies the validity of the gewisweb JWT token, and returns a SudoSOS
135
    * token if the GEWIS token is valid.
136
    * @operationId gewisWebAuthentication
137
    * @tags authenticate - Operations of authentication controller
138
    * @param {GewiswebAuthenticationRequest} request.body.required - The mock login.
139
    * @return {AuthenticationResponse} 200 - The created json web token.
140
    * @return {MessageResponse} 403 - The created json web token.
141
    * @return {string} 400 - Validation error.
142
    */
143
  public async gewiswebLogin(req: Request, res: Response): Promise<void> {
144
    const body = req.body as GewiswebAuthenticationRequest;
5✔
145

146
    try {
5✔
147
      let gewisweb: GewiswebToken;
148
      try {
5✔
149
        gewisweb = await util.promisify(jwt.verify)
5✔
150
          .bind(null, body.token, this.gewiswebSecret, {
151
            algorithms: ['HS512'],
152
            complete: false,
153
          })();
154
      } catch (error) {
155
        // Invalid token supplied.
156
        res.status(403).json({
2✔
157
          message: 'Invalid JWT signature',
158
        });
159
        return;
2✔
160
      }
161
      this.logger.trace('Gewisweb authentication for user with membership id', gewisweb.lidnr);
3✔
162

163
      let memberUser = await MemberUser.findOne({
3✔
164
        where: { memberId: gewisweb.lidnr },
165
        relations: UserService.getRelations<MemberUser>(),
166
      });
167
      if (!memberUser) {
3!
168
        this.logger.log('User not found in database, creating user');
×
169
        memberUser = await new Gewis().createUserFromWeb(gewisweb);
×
170
      } else {
171
        //
172
        const update = webResponseToUpdate(gewisweb);
3✔
173
        await UserService.updateUser(memberUser.user.id, { ...update, active: true });
3✔
174
      }
175

176
      // If a LOCAL_USER authenticates through GEWIS, implicitly convert the account back to a MEMBER account
177
      if (memberUser.user.type === UserType.LOCAL_USER) {
3✔
178
        await UserService.updateUserType(memberUser.user, UserType.MEMBER);
1✔
179
      }
180

181
      const response = await new AuthenticationService().getSaltedToken({
3✔
182
        user: memberUser.user,
183
        context: { roleManager: this.roleManager, tokenHandler: this.tokenHandler },
184
        salt: body.nonce,
185
      });
186
      res.json(response);
3✔
187
    } catch (error) {
188
      this.logger.error('Could not create token:', error);
×
189
      res.status(500).json('Internal server error.');
×
190
    }
191
  }
192

193
  /**
194
   * POST /authentication/GEWIS/LDAP
195
   * @summary LDAP login and hand out token
196
   *    If user has never signed in before this also creates an GEWIS account.
197
   * @operationId gewisLDAPAuthentication
198
   * @tags authenticate - Operations of authentication controller
199
   * @param {AuthenticationLDAPRequest} request.body.required - The LDAP login.
200
   * @return {AuthenticationResponse} 200 - The created json web token.
201
   * @return {string} 400 - Validation error.
202
   * @return {string} 403 - Authentication error.
203
   */
204
  public async ldapLogin(req: Request, res: Response): Promise<void> {
205
    const body = req.body as AuthenticationLDAPRequest;
2✔
206
    this.logger.trace('GEWIS LDAP authentication for user', body.accountName);
2✔
207

208
    try {
2✔
209
      const gewisService = new Gewis();
2✔
210
      await AuthenticationController.LDAPLoginConstructor(this.roleManager, this.tokenHandler, gewisService.findOrCreateGEWISUserAndBind.bind(gewisService))(req, res);
2✔
211
    } catch (error) {
212
      this.logger.error('Could not authenticate using LDAP:', error);
×
213
      res.status(500).json('Internal server error.');
×
214
    }
215
  }
216

217
  /**
218
   * POST /authentication/GEWIS/pin
219
   * @deprecated Use /authentication/GEWIS/pin-secure instead
220
   * @summary PIN login and hand out token.
221
   * @operationId gewisPinAuthentication
222
   * @tags authenticate - Operations of authentication controller
223
   * @param {GEWISAuthenticationPinRequest} request.body.required - The PIN login.
224
   * @return {AuthenticationResponse} 200 - The created json web token.
225
   * @return {string} 400 - Validation error.
226
   * @return {string} 403 - Authentication error.
227
   */
228
  public async gewisPINLogin(req: Request, res: Response): Promise<void> {
229
    const { pin, gewisId } = req.body as GEWISAuthenticationPinRequest;
3✔
230
    this.logger.trace('GEWIS PIN authentication for user', gewisId);
3✔
231

232
    try {
3✔
233
      const memberUser = await MemberUser.findOne({
3✔
234
        where: { memberId: gewisId },
235
        relations: ['user'],
236
      });
237

238
      if (!memberUser) {
3✔
239
        res.status(403).json({
1✔
240
          message: `User ${gewisId} not registered`,
241
        });
242
        return;
1✔
243
      }
244
      await (AuthenticationController.PINLoginConstructor(this.roleManager, this.tokenHandler,
2✔
245
        pin, memberUser.user.id))(req, res);
246
    } catch (error) {
247
      this.logger.error('Could not authenticate using PIN:', error);
×
248
      res.status(500).json('Internal server error.');
×
249
    }
250
  }
251
}
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