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

u-wave / core / 20206618234

14 Dec 2025 10:29AM UTC coverage: 85.938% (-0.2%) from 86.187%
20206618234

Pull #728

github

web-flow
Merge f5bd07dd3 into 96ae4617b
Pull Request #728: Store express-session state in SQLite

998 of 1189 branches covered (83.94%)

Branch coverage included in aggregate %.

120 of 178 new or added lines in 3 files covered. (67.42%)

9 existing lines in 1 file now uncovered.

10394 of 12067 relevant lines covered (86.14%)

98.56 hits per line

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

92.92
/src/HttpApi.js
1
import fs from 'node:fs';
1✔
2
import http from 'node:http';
1✔
3
import { randomUUID } from 'node:crypto';
1✔
4
import express from 'express';
1✔
5
import bodyParser from 'body-parser';
1✔
6
import cookieParser from 'cookie-parser';
1✔
7
import cors from 'cors';
1✔
8
import helmet from 'helmet';
1✔
9
import session from 'express-session';
1✔
10
import qs from 'qs';
1✔
11
import { pinoHttp } from 'pino-http';
1✔
12

1✔
13
// routes
1✔
14
import authenticate from './routes/authenticate.js';
1✔
15
import bans from './routes/bans.js';
1✔
16
import search from './routes/search.js';
1✔
17
import server from './routes/server.js';
1✔
18
import users from './routes/users.js';
1✔
19
import now from './routes/now.js';
1✔
20
import imports from './routes/import.js';
1✔
21

1✔
22
// middleware
1✔
23
import addFullUrl from './middleware/addFullUrl.js';
1✔
24
import attachUwaveMeta from './middleware/attachUwaveMeta.js';
1✔
25
import rateLimit from './middleware/rateLimit.js';
1✔
26
import errorHandler from './middleware/errorHandler.js';
1✔
27

1✔
28
// utils
1✔
29
import AuthRegistry from './AuthRegistry.js';
1✔
30
import matchOrigin from './utils/matchOrigin.js';
1✔
31
import SqliteSessionStore from './utils/SqliteSessionStore.js';
1✔
32

1✔
33
const optionsSchema = JSON.parse(
1✔
34
  fs.readFileSync(new URL('./schemas/httpApi.json', import.meta.url), 'utf8'),
1✔
35
);
1✔
36

1✔
37
/**
1✔
38
 * @param {{ token: string, requestUrl: string }} options
1✔
39
 * @returns {import('nodemailer').SendMailOptions}
1✔
40
 */
1✔
41
function defaultCreatePasswordResetEmail({ token, requestUrl }) {
1✔
42
  const parsed = new URL(requestUrl);
1✔
43
  const { hostname } = parsed;
1✔
44
  const resetLink = new URL(`/reset/${token}`, parsed);
1✔
45
  return {
1✔
46
    from: `noreply@${hostname}`,
1✔
47
    subject: 'üWave Password Reset Request',
1✔
48
    text: `
1✔
49
      Hello,
1✔
50

1✔
51
      To reset your password, please visit:
1✔
52
      ${resetLink}
1✔
53
    `,
1✔
54
  };
1✔
55
}
1✔
56

1✔
57
/**
1✔
58
 * @typedef {express.Router & { authRegistry: AuthRegistry }} HttpApi
1✔
59
 */
1✔
60

1✔
61
/**
1✔
62
 * @typedef {object} HttpApiOptions - Static options for the HTTP API.
1✔
63
 * @prop {string|Buffer} secret
1✔
64
 * @prop {boolean} [helmet]
1✔
65
 * @prop {boolean | number | string} [trustProxy]
1✔
66
 * @prop {(error: Error) => void} [onError]
1✔
67
 * @prop {{ secret: string }} [recaptcha]
1✔
68
 * @prop {import('nodemailer').Transport} [mailTransport]
1✔
69
 * @prop {(options: { token: string, requestUrl: string }) =>
1✔
70
 *   import('nodemailer').SendMailOptions} [createPasswordResetEmail]
1✔
71
 * @typedef {object} HttpApiSettings - Runtime options for the HTTP API.
1✔
72
 * @prop {string[]} allowedOrigins
1✔
73
 */
1✔
74

1✔
75
/**
1✔
76
 * @param {import('./Uwave.js').Boot} uw
1✔
77
 * @param {HttpApiOptions} options
1✔
78
 */
1✔
79
async function httpApi(uw, options) {
154✔
80
  if (!options.secret) {
154!
UNCOV
81
    throw new TypeError('"options.secret" is empty. This option is used to sign authentication '
×
UNCOV
82
      + 'keys, and is required for security reasons.');
×
UNCOV
83
  }
×
84

154✔
85
  if (options.onError != null && typeof options.onError !== 'function') {
154!
86
    throw new TypeError('"options.onError" must be a function.');
×
UNCOV
87
  }
×
88

154✔
89
  const logger = uw.logger.child({
154✔
90
    ns: 'uwave:http-api',
154✔
91
    level: 'warn',
154✔
92
  });
154✔
93

154✔
94
  uw.config.register(optionsSchema['uw:key'], optionsSchema);
154✔
95

154✔
96
  /** @type {HttpApiSettings} */
154✔
97
  // @ts-expect-error TS2322: get() always returns a validated object here
154✔
98
  let runtimeOptions = await uw.config.get(optionsSchema['uw:key']);
154✔
99
  const unsubscribe = uw.config.subscribe('u-wave:api', /** @param {HttpApiSettings} settings */ (settings) => {
154✔
UNCOV
100
    runtimeOptions = settings;
×
101
  });
154✔
102

154✔
103
  logger.debug(runtimeOptions, 'start HttpApi');
154✔
104
  uw.httpApi = Object.assign(express.Router(), {
154✔
105
    authRegistry: new AuthRegistry(uw.redis),
154✔
106
  });
154✔
107

154✔
108
  uw.express = express();
154✔
109
  uw.express.set('query parser', /** @param {string} str */ (str) => qs.parse(str, { depth: 1 }));
154✔
110
  if (options.trustProxy != null) {
154!
UNCOV
111
    uw.express.set('trust proxy', options.trustProxy);
×
UNCOV
112
  }
×
113

154✔
114
  uw.httpApi
154✔
115
    .use(pinoHttp({
154✔
116
      genReqId: () => randomUUID(),
154✔
117
      quietReqLogger: true,
154✔
118
      logger,
154✔
119
    }))
154✔
120
    .use(bodyParser.json())
154✔
121
    .use(cookieParser())
154✔
122
    .use(session({
154✔
123
      secret: options.secret,
154✔
124
      resave: false,
154✔
125
      saveUninitialized: false,
154✔
126
      cookie: {
154✔
127
        secure: uw.express.get('env') === 'production',
154✔
128
        httpOnly: true,
154✔
129
      },
154✔
130
      store: new SqliteSessionStore(uw.db, uw.logger.child({ ns: 'uwave:sessions' })),
154✔
131
    }))
154✔
132
    .use(uw.passport.initialize())
154✔
133
    .use(addFullUrl())
154✔
134
    .use(attachUwaveMeta(uw.httpApi, uw))
154✔
135
    .use(uw.passport.authenticate('jwt', { session: false }))
154✔
136
    .use(uw.passport.session())
154✔
137
    .use(rateLimit('api-http', { max: 500, duration: 60 * 1000 }));
154✔
138

154✔
139
  uw.httpApi
154✔
140
    .use('/auth', authenticate(uw.passport, {
154✔
141
      secret: options.secret,
154✔
142
      mailTransport: options.mailTransport,
154✔
143
      recaptcha: options.recaptcha,
154✔
144
      createPasswordResetEmail:
154✔
145
        options.createPasswordResetEmail ?? defaultCreatePasswordResetEmail,
154✔
146
    }))
154✔
147
    .use('/bans', bans())
154✔
148
    .use('/import', imports())
154✔
149
    .use('/now', now())
154✔
150
    .use('/search', search())
154✔
151
    .use('/server', server())
154✔
152
    .use('/users', users());
154✔
153

154✔
154
  uw.server = http.createServer(uw.express);
154✔
155
  if (options.helmet !== false) {
154✔
156
    uw.express.use(helmet({
154✔
157
      referrerPolicy: {
154✔
158
        policy: ['origin-when-cross-origin'],
154✔
159
      },
154✔
160
    }));
154✔
161
  }
154✔
162

154✔
163
  /** @type {import('cors').CorsOptions} */
154✔
164
  const corsOptions = {
154✔
165
    origin(origin, callback) {
154✔
166
      callback(null, matchOrigin(origin, runtimeOptions.allowedOrigins));
277✔
167
    },
154✔
168
  };
154✔
169
  uw.express.options('/api/*path', cors(corsOptions));
154✔
170
  uw.express.use('/api', cors(corsOptions), uw.httpApi);
154✔
171
  // An older name
154✔
172
  uw.express.use('/v1', cors(corsOptions), uw.httpApi);
154✔
173

154✔
174
  uw.onClose(() => {
154✔
175
    unsubscribe();
154✔
176
    uw.server.close();
154✔
177
  });
154✔
178
}
154✔
179

1✔
180
/**
1✔
181
 * @param {import('./Uwave.js').Boot} uw
1✔
182
 */
1✔
183
async function errorHandling(uw) {
154✔
184
  uw.logger.debug({ ns: 'uwave:http-api' }, 'setup HTTP error handling');
154✔
185
  uw.httpApi.use(errorHandler({
154✔
186
    onError(_req, error) {
154✔
187
      if ('status' in error && typeof error.status === 'number' && error.status >= 400 && error.status < 500) {
122✔
188
        return;
122✔
189
      }
122✔
UNCOV
190

×
UNCOV
191
      uw.logger.error({ err: error, ns: 'uwave:http-api' });
×
192
    },
154✔
193
  }));
154✔
194
}
154✔
195

1✔
196
export default httpApi;
1✔
197
export { errorHandling };
1✔
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