fastify-txstate 3.6.7 → 3.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ import { type Readable } from 'stream';
2
+ export interface FileHandler {
3
+ init: () => Promise<void>;
4
+ put: (stream: Readable) => Promise<{
5
+ checksum: string;
6
+ size: number;
7
+ }>;
8
+ get: (checksum: string) => Readable;
9
+ remove: (checksum: string) => Promise<void>;
10
+ }
11
+ export declare class FileSystemHandler implements FileHandler {
12
+ #private;
13
+ options: {
14
+ tmpdir: string;
15
+ permdir: string;
16
+ };
17
+ constructor(options?: {
18
+ tmpdir?: string;
19
+ permdir?: string;
20
+ });
21
+ init(): Promise<void>;
22
+ get(checksum: string): import("fs").ReadStream;
23
+ exists(checksum: string): Promise<boolean>;
24
+ fileSize(checksum: string): Promise<number>;
25
+ put(stream: Readable): Promise<{
26
+ checksum: string;
27
+ size: number;
28
+ }>;
29
+ remove(checksum: string): Promise<void>;
30
+ }
31
+ export declare const fileHandler: FileSystemHandler;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fileHandler = exports.FileSystemHandler = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const fs_1 = require("fs");
6
+ const promises_1 = require("fs/promises");
7
+ const path_1 = require("path");
8
+ const promises_2 = require("stream/promises");
9
+ const txstate_utils_1 = require("txstate-utils");
10
+ class FileSystemHandler {
11
+ options;
12
+ constructor(options = {}) {
13
+ this.options = {
14
+ tmpdir: options.tmpdir ?? '/files/tmp/',
15
+ permdir: options.permdir ?? '/files/storage/'
16
+ };
17
+ if (!this.options.tmpdir.endsWith('/'))
18
+ this.options.tmpdir += '/';
19
+ if (!this.options.permdir.endsWith('/'))
20
+ this.options.permdir += '/';
21
+ }
22
+ #getTmpLocation() {
23
+ return `${this.options.tmpdir}${(0, txstate_utils_1.randomid)(12)}`;
24
+ }
25
+ #getFileLocation(checksum) {
26
+ return `${this.options.permdir}${checksum.slice(0, 1)}/${checksum.slice(1, 2)}/${checksum.slice(2)}`;
27
+ }
28
+ async #moveToPerm(tmp, checksum) {
29
+ const checksumpath = this.#getFileLocation(checksum);
30
+ await (0, promises_1.mkdir)((0, path_1.dirname)(checksumpath), { recursive: true });
31
+ await (0, promises_1.rename)(tmp, checksumpath);
32
+ }
33
+ async init() {
34
+ await (0, promises_1.mkdir)(this.options.tmpdir, { recursive: true });
35
+ await (0, promises_1.mkdir)(this.options.permdir, { recursive: true });
36
+ }
37
+ get(checksum) {
38
+ const filepath = this.#getFileLocation(checksum);
39
+ const stream = (0, fs_1.createReadStream)(filepath);
40
+ return stream;
41
+ }
42
+ async exists(checksum) {
43
+ const filepath = this.#getFileLocation(checksum);
44
+ return (await (0, txstate_utils_1.rescue)((0, promises_1.access)(filepath, promises_1.constants.R_OK), false)) ?? true;
45
+ }
46
+ async fileSize(checksum) {
47
+ const filepath = this.#getFileLocation(checksum);
48
+ const info = await (0, promises_1.stat)(filepath);
49
+ return info.size;
50
+ }
51
+ async put(stream) {
52
+ const tmp = this.#getTmpLocation();
53
+ const hash = (0, crypto_1.createHash)('sha256');
54
+ let size = 0;
55
+ stream.on('data', (data) => { hash.update(data); size += data.length; });
56
+ try {
57
+ const out = (0, fs_1.createWriteStream)(tmp);
58
+ const flushedPromise = new Promise((resolve, reject) => {
59
+ out.on('close', resolve);
60
+ out.on('error', reject);
61
+ });
62
+ await (0, promises_2.pipeline)(stream, out);
63
+ await flushedPromise;
64
+ const checksum = hash.digest('base64url');
65
+ const rereadhash = (0, crypto_1.createHash)('sha256');
66
+ const read = (0, fs_1.createReadStream)(tmp);
67
+ for await (const chunk of read) {
68
+ rereadhash.update(chunk);
69
+ }
70
+ const rereadsum = rereadhash.digest('base64url');
71
+ if (rereadsum !== checksum)
72
+ throw new Error('File did not write to disk correctly. Please try uploading again.');
73
+ await this.#moveToPerm(tmp, checksum);
74
+ return { checksum, size };
75
+ }
76
+ catch (e) {
77
+ await (0, txstate_utils_1.rescue)((0, promises_1.unlink)(tmp));
78
+ throw e;
79
+ }
80
+ }
81
+ async remove(checksum) {
82
+ const filepath = this.#getFileLocation(checksum);
83
+ try {
84
+ await (0, promises_1.unlink)(filepath);
85
+ }
86
+ catch (e) {
87
+ if (e.code === 'ENOENT')
88
+ console.warn('Tried to delete file with checksum', checksum, 'but it did not exist.');
89
+ else
90
+ console.warn(e);
91
+ }
92
+ }
93
+ }
94
+ exports.FileSystemHandler = FileSystemHandler;
95
+ exports.fileHandler = new FileSystemHandler();
package/lib/index.d.ts CHANGED
@@ -189,5 +189,6 @@ export default class Server {
189
189
  }
190
190
  export * from './analytics';
191
191
  export * from './error';
192
+ export * from './filestorage';
192
193
  export * from './unified-auth';
193
194
  export * from './postformdata';
package/lib/index.js CHANGED
@@ -417,5 +417,6 @@ this is log into this application and use dev tools to pull your token from the
417
417
  exports.default = Server;
418
418
  __exportStar(require("./analytics"), exports);
419
419
  __exportStar(require("./error"), exports);
420
+ __exportStar(require("./filestorage"), exports);
420
421
  __exportStar(require("./unified-auth"), exports);
421
422
  __exportStar(require("./postformdata"), exports);
@@ -196,9 +196,17 @@ function registerUaCookieRoutes(app) {
196
196
  const redirectUrl = req.auth?.issuerConfig?.logoutUrl && (0, txstate_utils_1.isNotBlank)(req.auth.token)
197
197
  ? `${req.auth.issuerConfig.logoutUrl.toString()}?unifiedJwt=${encodeURIComponent(req.auth.token)}`
198
198
  : (process.env.PUBLIC_URL || new URL('..', req.url).toString());
199
- return res
200
- .header('Set-Cookie', `${uaCookieName}=; Path=/; Secure; HttpOnly; SameSite=Lax; Expires=Thu, 01 Jan 1970 00:00:00 GMT`)
201
- .redirect(redirectUrl);
199
+ void res.header('Set-Cookie', `${uaCookieName}=; Path=/; Secure; HttpOnly; SameSite=Lax; Expires=Thu, 01 Jan 1970 00:00:00 GMT`);
200
+ return `<!DOCTYPE html>
201
+ <html lang="en">
202
+ <head>
203
+ <meta charset="UTF-8">
204
+ <meta http-equiv="refresh" content="0; url=${(0, txstate_utils_1.htmlEncode)(redirectUrl)}">
205
+ <title>Logging out...</title>
206
+ </head>
207
+ <body>
208
+ </body>
209
+ </html>`;
202
210
  });
203
211
  app.get('/.uaService', {
204
212
  schema: {
@@ -213,9 +221,18 @@ function registerUaCookieRoutes(app) {
213
221
  }
214
222
  }
215
223
  }, async (req, res) => {
216
- return res
217
- .header('Set-Cookie', `${uaCookieName}=${req.query.unifiedJwt}; Path=/; Secure; HttpOnly; SameSite=Lax`)
218
- .redirect(req.query.requestedUrl ?? (process.env.PUBLIC_URL || new URL('..', req.url).toString()));
224
+ void res.header('Set-Cookie', `${uaCookieName}=${req.query.unifiedJwt}; Path=/; Secure; HttpOnly; SameSite=Lax`);
225
+ void res.type('text/html');
226
+ return `<!DOCTYPE html>
227
+ <html lang="en">
228
+ <head>
229
+ <meta charset="UTF-8">
230
+ <meta http-equiv="refresh" content="0; url=${(0, txstate_utils_1.htmlEncode)(req.query.requestedUrl ?? (process.env.PUBLIC_URL || new URL('..', req.url).toString()))}">
231
+ <title>Logging in...</title>
232
+ </head>
233
+ <body>
234
+ </body>
235
+ </html>`;
219
236
  });
220
237
  /**
221
238
  * In the case of a client-side application that uses the UA cookie to authenticate,
package/lib-esm/index.js CHANGED
@@ -15,4 +15,6 @@ export const LoggingAnalyticsClient = ftxst.LoggingAnalyticsClient
15
15
  export const ElasticAnalyticsClient = ftxst.ElasticAnalyticsClient
16
16
  export const postFormData = ftxst.postFormData
17
17
  export const readableToWebReadable = ftxst.readableToWebReadable
18
+ export const FileSystemHandler = ftxst.FileSystemHandler
19
+ export const fileHandler = ftxst.fileHandler
18
20
  export default ftxst.default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify-txstate",
3
- "version": "3.6.7",
3
+ "version": "3.6.9",
4
4
  "description": "A small wrapper for fastify providing a set of common conventions & utility functions we use.",
5
5
  "exports": {
6
6
  ".": {
@@ -37,7 +37,7 @@
37
37
  "@fastify/multipart": "^8.0.0",
38
38
  "@types/chai": "^4.2.14",
39
39
  "@types/mocha": "^10.0.0",
40
- "@types/node": "^22.0.0",
40
+ "@types/node": "^24.0.0",
41
41
  "axios": "^1.6.8",
42
42
  "chai": "^4.2.0",
43
43
  "eslint-config-standard-with-typescript": "^43.0.0",