@fluxfiles/node 0.1.0 → 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +18 -0
- package/dist/index.mjs +18 -0
- package/package.json +2 -1
- package/src/token.ts +23 -1
- package/src/types.ts +10 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thai-pc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -7,9 +7,17 @@ including encrypted BYOB (Bring Your Own Bucket) credentials — without running
|
|
|
7
7
|
|
|
8
8
|
Zero runtime dependencies (built on `node:crypto`).
|
|
9
9
|
|
|
10
|
+
> **This package only issues tokens — it is not a backend.** You still run a
|
|
11
|
+
> FluxFiles **core service** (the file-manager backend that talks to storage; a
|
|
12
|
+
> PHP app, e.g. the Docker image) for the token to authenticate against.
|
|
13
|
+
> `@fluxfiles/node` simply removes the need for *your app* to be PHP in order to
|
|
14
|
+
> mint those tokens.
|
|
15
|
+
|
|
10
16
|
## Requirements
|
|
11
17
|
|
|
12
18
|
- Node.js 16+
|
|
19
|
+
- A running FluxFiles **core service** the issued tokens authenticate against
|
|
20
|
+
(the SDK/iframe `endpoint` points at it).
|
|
13
21
|
- The same **`FLUXFILES_SECRET`** your FluxFiles core server uses to verify tokens
|
|
14
22
|
(HS256, **must be ≥ 32 bytes**). Keep it server-side only.
|
|
15
23
|
|
|
@@ -107,6 +115,8 @@ app.get('/fluxfiles/token', (req, res) => {
|
|
|
107
115
|
|
|
108
116
|
`createToken` options: `secret?`, `userId`, `perms?`, `disks?`, `prefix?`,
|
|
109
117
|
`maxUploadMb?`, `allowedExt?`, `ttl?`, `ownerOnly?`, `maxStorageMb?`, `maxFiles?`.
|
|
118
|
+
Per-tenant overrides (omit to inherit the server default): `aiAutoTag?` (bool),
|
|
119
|
+
`rateRead?` / `rateWrite?` (req/min), `variants?` (`{ thumb?, medium?, large? }` px).
|
|
110
120
|
`createByobToken` replaces `disks` with `byobDisks` (a map of name → S3-compatible
|
|
111
121
|
config) and does not take `maxStorageMb`/`maxFiles` (matching the core).
|
|
112
122
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** A FluxFiles permission. */
|
|
2
|
-
type FluxPermission = 'read' | 'write' | 'delete';
|
|
1
|
+
/** A FluxFiles permission. `audit` gates reading the activity log. */
|
|
2
|
+
type FluxPermission = 'read' | 'write' | 'delete' | 'audit';
|
|
3
3
|
/**
|
|
4
4
|
* A BYOB (Bring Your Own Bucket) disk config. Encrypted into the JWT and
|
|
5
5
|
* decrypted only at runtime by the FluxFiles server. Only S3-compatible
|
|
@@ -33,6 +33,14 @@ interface BaseTokenOptions {
|
|
|
33
33
|
ttl?: number;
|
|
34
34
|
/** Restrict destructive ops to files the user uploaded. */
|
|
35
35
|
ownerOnly?: boolean;
|
|
36
|
+
/** Per-tenant AI auto-tag toggle. Omit to inherit the server default. */
|
|
37
|
+
aiAutoTag?: boolean;
|
|
38
|
+
/** Per-tenant read rate limit (requests/min). `0`/omitted = inherit server default. */
|
|
39
|
+
rateRead?: number;
|
|
40
|
+
/** Per-tenant write rate limit (requests/min). `0`/omitted = inherit server default. */
|
|
41
|
+
rateWrite?: number;
|
|
42
|
+
/** Per-tenant image variant widths, e.g. `{ thumb: 150, medium: 768, large: 1920 }`. Omit to inherit. */
|
|
43
|
+
variants?: Partial<Record<'thumb' | 'medium' | 'large', number>> | null;
|
|
36
44
|
}
|
|
37
45
|
interface CreateTokenOptions extends BaseTokenOptions {
|
|
38
46
|
/** Disk names the token may access. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** A FluxFiles permission. */
|
|
2
|
-
type FluxPermission = 'read' | 'write' | 'delete';
|
|
1
|
+
/** A FluxFiles permission. `audit` gates reading the activity log. */
|
|
2
|
+
type FluxPermission = 'read' | 'write' | 'delete' | 'audit';
|
|
3
3
|
/**
|
|
4
4
|
* A BYOB (Bring Your Own Bucket) disk config. Encrypted into the JWT and
|
|
5
5
|
* decrypted only at runtime by the FluxFiles server. Only S3-compatible
|
|
@@ -33,6 +33,14 @@ interface BaseTokenOptions {
|
|
|
33
33
|
ttl?: number;
|
|
34
34
|
/** Restrict destructive ops to files the user uploaded. */
|
|
35
35
|
ownerOnly?: boolean;
|
|
36
|
+
/** Per-tenant AI auto-tag toggle. Omit to inherit the server default. */
|
|
37
|
+
aiAutoTag?: boolean;
|
|
38
|
+
/** Per-tenant read rate limit (requests/min). `0`/omitted = inherit server default. */
|
|
39
|
+
rateRead?: number;
|
|
40
|
+
/** Per-tenant write rate limit (requests/min). `0`/omitted = inherit server default. */
|
|
41
|
+
rateWrite?: number;
|
|
42
|
+
/** Per-tenant image variant widths, e.g. `{ thumb: 150, medium: 768, large: 1920 }`. Omit to inherit. */
|
|
43
|
+
variants?: Partial<Record<'thumb' | 'medium' | 'large', number>> | null;
|
|
36
44
|
}
|
|
37
45
|
interface CreateTokenOptions extends BaseTokenOptions {
|
|
38
46
|
/** Disk names the token may access. */
|
package/dist/index.js
CHANGED
|
@@ -98,6 +98,7 @@ function createToken(opts) {
|
|
|
98
98
|
max_files: opts.maxFiles ?? 0
|
|
99
99
|
};
|
|
100
100
|
if (opts.ownerOnly) payload.owner_only = true;
|
|
101
|
+
applyTenantOverrides(payload, opts);
|
|
101
102
|
return sign(payload, secret);
|
|
102
103
|
}
|
|
103
104
|
function createByobToken(opts) {
|
|
@@ -123,8 +124,25 @@ function createByobToken(opts) {
|
|
|
123
124
|
byob_disks: encrypted
|
|
124
125
|
};
|
|
125
126
|
if (opts.ownerOnly) payload.owner_only = true;
|
|
127
|
+
applyTenantOverrides(payload, opts);
|
|
126
128
|
return sign(payload, secret);
|
|
127
129
|
}
|
|
130
|
+
function sanitizeVariants(v) {
|
|
131
|
+
if (!v || typeof v !== "object") return null;
|
|
132
|
+
const out = {};
|
|
133
|
+
for (const name of ["thumb", "medium", "large"]) {
|
|
134
|
+
const w = Math.trunc(Number(v[name]));
|
|
135
|
+
if (Number.isFinite(w) && w >= 16 && w <= 8e3) out[name] = w;
|
|
136
|
+
}
|
|
137
|
+
return Object.keys(out).length ? out : null;
|
|
138
|
+
}
|
|
139
|
+
function applyTenantOverrides(payload, opts) {
|
|
140
|
+
if (opts.aiAutoTag !== void 0) payload.ai_auto_tag = !!opts.aiAutoTag;
|
|
141
|
+
if (opts.rateRead && opts.rateRead > 0) payload.rate_read = Math.trunc(opts.rateRead);
|
|
142
|
+
if (opts.rateWrite && opts.rateWrite > 0) payload.rate_write = Math.trunc(opts.rateWrite);
|
|
143
|
+
const variants = sanitizeVariants(opts.variants);
|
|
144
|
+
if (variants) payload.variants = variants;
|
|
145
|
+
}
|
|
128
146
|
function validateByobDisk(name, config) {
|
|
129
147
|
if (!config || config.driver !== "s3") {
|
|
130
148
|
throw new Error(`FluxFiles BYOB disk "${name}": driver must be "s3" (the server rejects "local").`);
|
package/dist/index.mjs
CHANGED
|
@@ -76,6 +76,7 @@ function createToken(opts) {
|
|
|
76
76
|
max_files: opts.maxFiles ?? 0
|
|
77
77
|
};
|
|
78
78
|
if (opts.ownerOnly) payload.owner_only = true;
|
|
79
|
+
applyTenantOverrides(payload, opts);
|
|
79
80
|
return sign(payload, secret);
|
|
80
81
|
}
|
|
81
82
|
function createByobToken(opts) {
|
|
@@ -101,8 +102,25 @@ function createByobToken(opts) {
|
|
|
101
102
|
byob_disks: encrypted
|
|
102
103
|
};
|
|
103
104
|
if (opts.ownerOnly) payload.owner_only = true;
|
|
105
|
+
applyTenantOverrides(payload, opts);
|
|
104
106
|
return sign(payload, secret);
|
|
105
107
|
}
|
|
108
|
+
function sanitizeVariants(v) {
|
|
109
|
+
if (!v || typeof v !== "object") return null;
|
|
110
|
+
const out = {};
|
|
111
|
+
for (const name of ["thumb", "medium", "large"]) {
|
|
112
|
+
const w = Math.trunc(Number(v[name]));
|
|
113
|
+
if (Number.isFinite(w) && w >= 16 && w <= 8e3) out[name] = w;
|
|
114
|
+
}
|
|
115
|
+
return Object.keys(out).length ? out : null;
|
|
116
|
+
}
|
|
117
|
+
function applyTenantOverrides(payload, opts) {
|
|
118
|
+
if (opts.aiAutoTag !== void 0) payload.ai_auto_tag = !!opts.aiAutoTag;
|
|
119
|
+
if (opts.rateRead && opts.rateRead > 0) payload.rate_read = Math.trunc(opts.rateRead);
|
|
120
|
+
if (opts.rateWrite && opts.rateWrite > 0) payload.rate_write = Math.trunc(opts.rateWrite);
|
|
121
|
+
const variants = sanitizeVariants(opts.variants);
|
|
122
|
+
if (variants) payload.variants = variants;
|
|
123
|
+
}
|
|
106
124
|
function validateByobDisk(name, config) {
|
|
107
125
|
if (!config || config.driver !== "s3") {
|
|
108
126
|
throw new Error(`FluxFiles BYOB disk "${name}": driver must be "s3" (the server rejects "local").`);
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluxfiles/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Server-side Node/TypeScript SDK for minting FluxFiles JWTs (plain + BYOB), byte-compatible with the PHP core",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"module": "dist/index.mjs",
|
|
8
9
|
"types": "dist/index.d.ts",
|
package/src/token.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { base64url, hmacSha256, encryptByob } from './crypto';
|
|
3
|
-
import type { ByobDiskConfig, CreateByobTokenOptions, CreateTokenOptions } from './types';
|
|
3
|
+
import type { BaseTokenOptions, ByobDiskConfig, CreateByobTokenOptions, CreateTokenOptions } from './types';
|
|
4
4
|
|
|
5
5
|
const MIN_SECRET_BYTES = 32;
|
|
6
6
|
|
|
@@ -48,6 +48,7 @@ export function createToken(opts: CreateTokenOptions): string {
|
|
|
48
48
|
max_files: opts.maxFiles ?? 0,
|
|
49
49
|
};
|
|
50
50
|
if (opts.ownerOnly) payload.owner_only = true;
|
|
51
|
+
applyTenantOverrides(payload, opts);
|
|
51
52
|
return sign(payload, secret);
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -81,9 +82,30 @@ export function createByobToken(opts: CreateByobTokenOptions): string {
|
|
|
81
82
|
byob_disks: encrypted,
|
|
82
83
|
};
|
|
83
84
|
if (opts.ownerOnly) payload.owner_only = true;
|
|
85
|
+
applyTenantOverrides(payload, opts);
|
|
84
86
|
return sign(payload, secret);
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
/** Sanitize the per-tenant `variants` claim — matches PHP `Claims::sanitizeVariants`. */
|
|
90
|
+
function sanitizeVariants(v: BaseTokenOptions['variants']): Record<string, number> | null {
|
|
91
|
+
if (!v || typeof v !== 'object') return null;
|
|
92
|
+
const out: Record<string, number> = {};
|
|
93
|
+
for (const name of ['thumb', 'medium', 'large'] as const) {
|
|
94
|
+
const w = Math.trunc(Number((v as Record<string, unknown>)[name]));
|
|
95
|
+
if (Number.isFinite(w) && w >= 16 && w <= 8000) out[name] = w;
|
|
96
|
+
}
|
|
97
|
+
return Object.keys(out).length ? out : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Copy the optional per-tenant override claims into a payload when set. */
|
|
101
|
+
function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenOptions): void {
|
|
102
|
+
if (opts.aiAutoTag !== undefined) payload.ai_auto_tag = !!opts.aiAutoTag;
|
|
103
|
+
if (opts.rateRead && opts.rateRead > 0) payload.rate_read = Math.trunc(opts.rateRead);
|
|
104
|
+
if (opts.rateWrite && opts.rateWrite > 0) payload.rate_write = Math.trunc(opts.rateWrite);
|
|
105
|
+
const variants = sanitizeVariants(opts.variants);
|
|
106
|
+
if (variants) payload.variants = variants;
|
|
107
|
+
}
|
|
108
|
+
|
|
87
109
|
/**
|
|
88
110
|
* Light client-side validation. The server independently re-validates (incl.
|
|
89
111
|
* SSRF checks on the endpoint), so this only catches obvious mistakes early.
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** A FluxFiles permission. */
|
|
2
|
-
export type FluxPermission = 'read' | 'write' | 'delete';
|
|
1
|
+
/** A FluxFiles permission. `audit` gates reading the activity log. */
|
|
2
|
+
export type FluxPermission = 'read' | 'write' | 'delete' | 'audit';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A BYOB (Bring Your Own Bucket) disk config. Encrypted into the JWT and
|
|
@@ -35,6 +35,14 @@ export interface BaseTokenOptions {
|
|
|
35
35
|
ttl?: number;
|
|
36
36
|
/** Restrict destructive ops to files the user uploaded. */
|
|
37
37
|
ownerOnly?: boolean;
|
|
38
|
+
/** Per-tenant AI auto-tag toggle. Omit to inherit the server default. */
|
|
39
|
+
aiAutoTag?: boolean;
|
|
40
|
+
/** Per-tenant read rate limit (requests/min). `0`/omitted = inherit server default. */
|
|
41
|
+
rateRead?: number;
|
|
42
|
+
/** Per-tenant write rate limit (requests/min). `0`/omitted = inherit server default. */
|
|
43
|
+
rateWrite?: number;
|
|
44
|
+
/** Per-tenant image variant widths, e.g. `{ thumb: 150, medium: 768, large: 1920 }`. Omit to inherit. */
|
|
45
|
+
variants?: Partial<Record<'thumb' | 'medium' | 'large', number>> | null;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export interface CreateTokenOptions extends BaseTokenOptions {
|