@axium/server 0.9.0 → 0.10.0
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/{web/api/index.ts → dist/api/index.d.ts} +0 -2
- package/dist/api/index.js +5 -0
- package/dist/api/metadata.d.ts +1 -0
- package/dist/api/metadata.js +28 -0
- package/dist/api/passkeys.d.ts +1 -0
- package/dist/api/passkeys.js +50 -0
- package/dist/api/register.d.ts +1 -0
- package/dist/api/register.js +70 -0
- package/dist/api/session.d.ts +1 -0
- package/dist/api/session.js +31 -0
- package/dist/api/users.d.ts +1 -0
- package/dist/api/users.js +244 -0
- package/dist/apps.d.ts +0 -5
- package/dist/apps.js +2 -9
- package/dist/auth.d.ts +9 -21
- package/dist/auth.js +12 -18
- package/dist/cli.js +65 -26
- package/dist/config.d.ts +15 -8
- package/dist/config.js +38 -15
- package/dist/database.js +4 -0
- package/dist/io.d.ts +6 -4
- package/dist/io.js +26 -19
- package/dist/plugins.d.ts +4 -2
- package/dist/plugins.js +15 -14
- package/dist/requests.d.ts +11 -0
- package/dist/requests.js +58 -0
- package/dist/routes.d.ts +12 -13
- package/dist/routes.js +21 -22
- package/dist/serve.d.ts +7 -0
- package/dist/serve.js +11 -0
- package/dist/state.d.ts +4 -0
- package/dist/state.js +22 -0
- package/dist/sveltekit.d.ts +8 -0
- package/dist/sveltekit.js +90 -0
- package/package.json +10 -5
- package/svelte.config.js +36 -0
- package/web/hooks.server.ts +8 -3
- package/web/lib/Dialog.svelte +0 -1
- package/web/lib/FormDialog.svelte +0 -1
- package/web/lib/icons/Icon.svelte +2 -7
- package/web/template.html +18 -0
- package/web/tsconfig.json +3 -2
- package/web/api/metadata.ts +0 -35
- package/web/api/passkeys.ts +0 -56
- package/web/api/readme.md +0 -1
- package/web/api/register.ts +0 -83
- package/web/api/schemas.ts +0 -22
- package/web/api/session.ts +0 -33
- package/web/api/users.ts +0 -351
- package/web/api/utils.ts +0 -66
- package/web/app.html +0 -14
- package/web/auth.ts +0 -8
- package/web/index.server.ts +0 -1
- package/web/index.ts +0 -1
- package/web/lib/auth.ts +0 -12
- package/web/lib/index.ts +0 -5
- package/web/routes/+layout.svelte +0 -6
- package/web/routes/[...path]/+page.server.ts +0 -13
- package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
- package/web/routes/api/[...path]/+server.ts +0 -49
- package/web/utils.ts +0 -26
- /package/{web/lib → assets}/icons/light.svg +0 -0
- /package/{web/lib → assets}/icons/regular.svg +0 -0
- /package/{web/lib → assets}/icons/solid.svg +0 -0
- /package/{web/lib → assets}/styles.css +0 -0
- /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
- /package/{web/routes → routes}/account/+page.svelte +0 -0
- /package/{web/routes → routes}/login/+page.svelte +0 -0
- /package/{web/routes → routes}/logout/+page.svelte +0 -0
- /package/{web/routes → routes}/register/+page.svelte +0 -0
package/dist/cli.js
CHANGED
|
@@ -52,14 +52,16 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
52
52
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
53
|
});
|
|
54
54
|
import { Argument, Option, program } from 'commander';
|
|
55
|
+
import { join } from 'node:path/posix';
|
|
55
56
|
import { styleText } from 'node:util';
|
|
56
57
|
import { getByString, isJSON, setByString } from 'utilium';
|
|
57
58
|
import $pkg from '../package.json' with { type: 'json' };
|
|
58
|
-
import config from './config.js';
|
|
59
|
-
import * as db from './database.js';
|
|
60
|
-
import { _portActions, _portMethods, exit, handleError, output, restrictedPorts } from './io.js';
|
|
61
|
-
import { loadDefaultPlugins, plugins, pluginText, resolvePlugin } from './plugins.js';
|
|
62
59
|
import { apps } from './apps.js';
|
|
60
|
+
import config, { configFiles, saveConfigTo } from './config.js';
|
|
61
|
+
import * as db from './database.js';
|
|
62
|
+
import { _portActions, _portMethods, defaultOutput, exit, handleError, output, restrictedPorts } from './io.js';
|
|
63
|
+
import { getSpecifier, plugins, pluginText, resolvePlugin } from './plugins.js';
|
|
64
|
+
import { serve } from './serve.js';
|
|
63
65
|
program
|
|
64
66
|
.version($pkg.version)
|
|
65
67
|
.name('axium')
|
|
@@ -72,15 +74,10 @@ program.on('option:debug', () => config.set({ debug: true }));
|
|
|
72
74
|
program.on('option:config', () => void config.load(program.opts().config));
|
|
73
75
|
program.hook('preAction', async function (_, action) {
|
|
74
76
|
await config.loadDefaults();
|
|
75
|
-
await loadDefaultPlugins();
|
|
76
77
|
const opt = action.optsWithGlobals();
|
|
77
78
|
opt.force && output.warn('--force: Protections disabled.');
|
|
78
79
|
if (opt.debug === false)
|
|
79
80
|
config.set({ debug: false });
|
|
80
|
-
/* if (!config.auth.secret) {
|
|
81
|
-
config.save({ auth: { secret: process.env.AUTH_SECRET || randomBytes(32).toString('base64') } }, true);
|
|
82
|
-
output.debug('Auto-generated a new auth secret');
|
|
83
|
-
} */
|
|
84
81
|
});
|
|
85
82
|
// Options shared by multiple (sub)commands
|
|
86
83
|
const opts = {
|
|
@@ -255,11 +252,7 @@ axiumConfig
|
|
|
255
252
|
for (const path of config.files.keys())
|
|
256
253
|
console.log(path);
|
|
257
254
|
});
|
|
258
|
-
const axiumPlugin = program
|
|
259
|
-
.command('plugins')
|
|
260
|
-
.description('Manage plugins')
|
|
261
|
-
.addOption(opts.global)
|
|
262
|
-
.option('--safe', 'do not perform actions that would execute code from plugins.');
|
|
255
|
+
const axiumPlugin = program.command('plugins').description('Manage plugins').addOption(opts.global);
|
|
263
256
|
axiumPlugin
|
|
264
257
|
.command('list')
|
|
265
258
|
.alias('ls')
|
|
@@ -279,7 +272,7 @@ axiumPlugin
|
|
|
279
272
|
}
|
|
280
273
|
console.log(styleText('whiteBright', plugins.size + ' plugin(s) loaded:'));
|
|
281
274
|
for (const plugin of plugins) {
|
|
282
|
-
console.log(plugin.name,
|
|
275
|
+
console.log(plugin.name, opt.versions ? plugin.version : '');
|
|
283
276
|
}
|
|
284
277
|
});
|
|
285
278
|
axiumPlugin
|
|
@@ -292,6 +285,38 @@ axiumPlugin
|
|
|
292
285
|
exit(`Can't find a plugin matching "${search}"`);
|
|
293
286
|
console.log(pluginText(plugin));
|
|
294
287
|
});
|
|
288
|
+
axiumPlugin
|
|
289
|
+
.command('remove')
|
|
290
|
+
.alias('rm')
|
|
291
|
+
.description('Remove a plugin')
|
|
292
|
+
.argument('<plugin>', 'the plugin to remove')
|
|
293
|
+
.action(async (search, opt) => {
|
|
294
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
295
|
+
try {
|
|
296
|
+
const plugin = resolvePlugin(search);
|
|
297
|
+
if (!plugin)
|
|
298
|
+
exit(`Can't find a plugin matching "${search}"`);
|
|
299
|
+
const specifier = getSpecifier(plugin);
|
|
300
|
+
const _ = __addDisposableResource(env_4, db.connect(), true);
|
|
301
|
+
await plugin.db_remove?.({ ...opt, output: defaultOutput }, db.database);
|
|
302
|
+
for (const [path, data] of configFiles) {
|
|
303
|
+
if (!data.plugins)
|
|
304
|
+
continue;
|
|
305
|
+
data.plugins = data.plugins.filter(p => p !== specifier);
|
|
306
|
+
saveConfigTo(path, data);
|
|
307
|
+
}
|
|
308
|
+
plugins.delete(plugin);
|
|
309
|
+
}
|
|
310
|
+
catch (e_4) {
|
|
311
|
+
env_4.error = e_4;
|
|
312
|
+
env_4.hasError = true;
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
const result_4 = __disposeResources(env_4);
|
|
316
|
+
if (result_4)
|
|
317
|
+
await result_4;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
295
320
|
const axiumApps = program.command('apps').description('Manage Axium apps').addOption(opts.global);
|
|
296
321
|
axiumApps
|
|
297
322
|
.command('list')
|
|
@@ -319,22 +344,21 @@ program
|
|
|
319
344
|
.description('Get information about the server')
|
|
320
345
|
.addOption(opts.host)
|
|
321
346
|
.action(async () => {
|
|
322
|
-
const
|
|
347
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
323
348
|
try {
|
|
324
349
|
console.log('Axium Server v' + program.version());
|
|
325
350
|
console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
326
351
|
console.log(styleText('whiteBright', 'Loaded config files:'), config.files.keys().toArray().join(', '));
|
|
327
352
|
process.stdout.write(styleText('whiteBright', 'Database: '));
|
|
328
|
-
const _ = __addDisposableResource(
|
|
353
|
+
const _ = __addDisposableResource(env_5, db.connect(), true);
|
|
329
354
|
try {
|
|
330
355
|
console.log(await db.statusText());
|
|
331
356
|
}
|
|
332
357
|
catch {
|
|
333
358
|
output.error('Unavailable');
|
|
334
359
|
}
|
|
335
|
-
console.log(styleText('whiteBright', 'Credentials authentication:'), config.auth.credentials ? styleText('yellow', 'enabled') : 'disabled');
|
|
336
360
|
console.log(styleText('whiteBright', 'Loaded plugins:'), Array.from(plugins)
|
|
337
|
-
.map(plugin => plugin.
|
|
361
|
+
.map(plugin => plugin.name)
|
|
338
362
|
.join(', ') || styleText('dim', '(none)'));
|
|
339
363
|
for (const plugin of plugins) {
|
|
340
364
|
if (!plugin.statusText)
|
|
@@ -343,14 +367,14 @@ program
|
|
|
343
367
|
console.log(await plugin.statusText());
|
|
344
368
|
}
|
|
345
369
|
}
|
|
346
|
-
catch (
|
|
347
|
-
|
|
348
|
-
|
|
370
|
+
catch (e_5) {
|
|
371
|
+
env_5.error = e_5;
|
|
372
|
+
env_5.hasError = true;
|
|
349
373
|
}
|
|
350
374
|
finally {
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
await
|
|
375
|
+
const result_5 = __disposeResources(env_5);
|
|
376
|
+
if (result_5)
|
|
377
|
+
await result_5;
|
|
354
378
|
}
|
|
355
379
|
});
|
|
356
380
|
program
|
|
@@ -368,8 +392,23 @@ program
|
|
|
368
392
|
.addOption(opts.force)
|
|
369
393
|
.addOption(opts.host)
|
|
370
394
|
.action(async (opt) => {
|
|
371
|
-
/* config.save({ auth: { secret: randomBytes(32).toString('base64') } }, true); */
|
|
372
395
|
await db.init({ ...opt, skip: opt.dbSkip }).catch(handleError);
|
|
373
396
|
await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(handleError);
|
|
374
397
|
});
|
|
398
|
+
program
|
|
399
|
+
.command('serve')
|
|
400
|
+
.description('Start the Axium server')
|
|
401
|
+
.option('-p, --port <port>', 'the port to listen on')
|
|
402
|
+
.option('--ssl <prefix>', 'the prefix for the cert.pem and key.pem SSL files')
|
|
403
|
+
.action(async (opt) => {
|
|
404
|
+
const server = await serve({
|
|
405
|
+
secure: opt.ssl ? true : config.web.secure,
|
|
406
|
+
ssl_cert: opt.ssl ? join(opt.ssl, 'cert.pem') : config.web.ssl_cert,
|
|
407
|
+
ssl_key: opt.ssl ? join(opt.ssl, 'key.pem') : config.web.ssl_key,
|
|
408
|
+
});
|
|
409
|
+
const port = !Number.isNaN(Number.parseInt(opt.port ?? '')) ? Number.parseInt(opt.port) : config.web.port;
|
|
410
|
+
server.listen(port, () => {
|
|
411
|
+
console.log('Server is listening on port ' + port);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
375
414
|
program.parse();
|
package/dist/config.d.ts
CHANGED
|
@@ -10,8 +10,6 @@ export interface Config extends Record<string, unknown> {
|
|
|
10
10
|
disabled: string[];
|
|
11
11
|
};
|
|
12
12
|
auth: {
|
|
13
|
-
credentials: boolean;
|
|
14
|
-
debug: boolean;
|
|
15
13
|
origin: string;
|
|
16
14
|
/** In minutes */
|
|
17
15
|
passkey_probation: number;
|
|
@@ -37,19 +35,24 @@ export interface Config extends Record<string, unknown> {
|
|
|
37
35
|
};
|
|
38
36
|
web: {
|
|
39
37
|
prefix: string;
|
|
38
|
+
assets: string;
|
|
39
|
+
secure: boolean;
|
|
40
|
+
port: number;
|
|
41
|
+
ssl_key: string;
|
|
42
|
+
ssl_cert: string;
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
|
-
export declare const configFiles: Map<string,
|
|
45
|
+
export declare const configFiles: Map<string, File>;
|
|
43
46
|
export declare function plainConfig(): Omit<Config, keyof typeof configShortcuts>;
|
|
44
47
|
declare const configShortcuts: {
|
|
45
|
-
findPath: typeof
|
|
48
|
+
findPath: typeof findConfigPaths;
|
|
46
49
|
load: typeof loadConfig;
|
|
47
50
|
loadDefaults: typeof loadDefaultConfigs;
|
|
48
51
|
plain: typeof plainConfig;
|
|
49
52
|
save: typeof saveConfig;
|
|
50
53
|
saveTo: typeof saveConfigTo;
|
|
51
54
|
set: typeof setConfig;
|
|
52
|
-
files: Map<string,
|
|
55
|
+
files: Map<string, File>;
|
|
53
56
|
};
|
|
54
57
|
export declare const config: Config & typeof configShortcuts;
|
|
55
58
|
export default config;
|
|
@@ -62,8 +65,6 @@ export declare const File: z.ZodObject<{
|
|
|
62
65
|
disabled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
63
66
|
}, z.core.$strip>>;
|
|
64
67
|
auth: z.ZodOptional<z.ZodObject<{
|
|
65
|
-
credentials: z.ZodOptional<z.ZodBoolean>;
|
|
66
|
-
debug: z.ZodOptional<z.ZodBoolean>;
|
|
67
68
|
origin: z.ZodOptional<z.ZodString>;
|
|
68
69
|
passkey_probation: z.ZodOptional<z.ZodNumber>;
|
|
69
70
|
rp_id: z.ZodOptional<z.ZodString>;
|
|
@@ -92,12 +93,18 @@ export declare const File: z.ZodObject<{
|
|
|
92
93
|
}, z.core.$strip>>;
|
|
93
94
|
web: z.ZodOptional<z.ZodObject<{
|
|
94
95
|
prefix: z.ZodOptional<z.ZodString>;
|
|
96
|
+
assets: z.ZodOptional<z.ZodString>;
|
|
97
|
+
secure: z.ZodOptional<z.ZodBoolean>;
|
|
98
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
99
|
+
ssl_key: z.ZodOptional<z.ZodString>;
|
|
100
|
+
ssl_cert: z.ZodOptional<z.ZodString>;
|
|
95
101
|
}, z.core.$strip>>;
|
|
96
102
|
include: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
97
103
|
plugins: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
98
104
|
}, z.core.$loose>;
|
|
99
105
|
export interface File extends PartialRecursive<Config>, z.infer<typeof File> {
|
|
100
106
|
}
|
|
107
|
+
export declare function addConfigDefaults(other: PartialRecursive<Config>, _target?: Record<string, any>): void;
|
|
101
108
|
/**
|
|
102
109
|
* Update the current config
|
|
103
110
|
*/
|
|
@@ -132,4 +139,4 @@ export declare function saveConfigTo(path: string, changed: PartialRecursive<Con
|
|
|
132
139
|
/**
|
|
133
140
|
* Find the path to the config file
|
|
134
141
|
*/
|
|
135
|
-
export declare function
|
|
142
|
+
export declare function findConfigPaths(): string[];
|
package/dist/config.js
CHANGED
|
@@ -3,14 +3,15 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path/posix';
|
|
4
4
|
import { deepAssign, omit } from 'utilium';
|
|
5
5
|
import * as z from 'zod/v4';
|
|
6
|
-
import {
|
|
6
|
+
import { _setDebugOutput, dirs, logger, output } from './io.js';
|
|
7
7
|
import { loadPlugin } from './plugins.js';
|
|
8
|
-
|
|
8
|
+
import { _unique } from './state.js';
|
|
9
|
+
export const configFiles = _unique('configFiles', new Map());
|
|
9
10
|
export function plainConfig() {
|
|
10
11
|
return omit(config, Object.keys(configShortcuts));
|
|
11
12
|
}
|
|
12
13
|
const configShortcuts = {
|
|
13
|
-
findPath:
|
|
14
|
+
findPath: findConfigPaths,
|
|
14
15
|
load: loadConfig,
|
|
15
16
|
loadDefaults: loadDefaultConfigs,
|
|
16
17
|
plain: plainConfig,
|
|
@@ -19,18 +20,16 @@ const configShortcuts = {
|
|
|
19
20
|
set: setConfig,
|
|
20
21
|
files: configFiles,
|
|
21
22
|
};
|
|
22
|
-
export const config = {
|
|
23
|
+
export const config = _unique('config', {
|
|
23
24
|
...configShortcuts,
|
|
24
25
|
api: {
|
|
25
26
|
disable_metadata: false,
|
|
26
|
-
cookie_auth:
|
|
27
|
+
cookie_auth: true,
|
|
27
28
|
},
|
|
28
29
|
apps: {
|
|
29
30
|
disabled: [],
|
|
30
31
|
},
|
|
31
32
|
auth: {
|
|
32
|
-
credentials: false,
|
|
33
|
-
debug: false,
|
|
34
33
|
origin: 'https://test.localhost',
|
|
35
34
|
passkey_probation: 60,
|
|
36
35
|
rp_id: 'test.localhost',
|
|
@@ -53,8 +52,13 @@ export const config = {
|
|
|
53
52
|
},
|
|
54
53
|
web: {
|
|
55
54
|
prefix: '',
|
|
55
|
+
assets: '',
|
|
56
|
+
secure: true,
|
|
57
|
+
port: 443,
|
|
58
|
+
ssl_key: resolve(dirs[0], 'ssl_key.pem'),
|
|
59
|
+
ssl_cert: resolve(dirs[0], 'ssl_cert.pem'),
|
|
56
60
|
},
|
|
57
|
-
};
|
|
61
|
+
});
|
|
58
62
|
export default config;
|
|
59
63
|
// config from file
|
|
60
64
|
export const File = z
|
|
@@ -72,8 +76,6 @@ export const File = z
|
|
|
72
76
|
.partial(),
|
|
73
77
|
auth: z
|
|
74
78
|
.object({
|
|
75
|
-
credentials: z.boolean(),
|
|
76
|
-
debug: z.boolean(),
|
|
77
79
|
origin: z.string(),
|
|
78
80
|
/** In minutes */
|
|
79
81
|
passkey_probation: z.number(),
|
|
@@ -105,12 +107,28 @@ export const File = z
|
|
|
105
107
|
web: z
|
|
106
108
|
.object({
|
|
107
109
|
prefix: z.string(),
|
|
110
|
+
assets: z.string(),
|
|
111
|
+
secure: z.boolean(),
|
|
112
|
+
port: z.number().min(1).max(65535),
|
|
113
|
+
ssl_key: z.string(),
|
|
114
|
+
ssl_cert: z.string(),
|
|
108
115
|
})
|
|
109
116
|
.partial(),
|
|
110
117
|
include: z.array(z.string()).optional(),
|
|
111
118
|
plugins: z.array(z.string()).optional(),
|
|
112
119
|
})
|
|
113
120
|
.partial();
|
|
121
|
+
export function addConfigDefaults(other, _target = config) {
|
|
122
|
+
for (const [key, value] of Object.entries(other)) {
|
|
123
|
+
if (!(key in _target) || _target[key] === null || _target[key] === undefined || Number.isNaN(_target[key])) {
|
|
124
|
+
_target[key] = value;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (typeof value == 'object' && value != null && typeof _target[key] == 'object') {
|
|
128
|
+
addConfigDefaults(value, _target[key]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
114
132
|
/**
|
|
115
133
|
* Update the current config
|
|
116
134
|
*/
|
|
@@ -119,11 +137,14 @@ export function setConfig(other) {
|
|
|
119
137
|
logger.detach(output);
|
|
120
138
|
if (config.log.console)
|
|
121
139
|
logger.attach(output, { output: config.log.level });
|
|
140
|
+
_setDebugOutput(config.debug);
|
|
122
141
|
}
|
|
123
142
|
/**
|
|
124
143
|
* Load the config from the provided path
|
|
125
144
|
*/
|
|
126
145
|
export async function loadConfig(path, options = {}) {
|
|
146
|
+
if (configFiles.has(path))
|
|
147
|
+
return;
|
|
127
148
|
let json;
|
|
128
149
|
try {
|
|
129
150
|
json = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -137,13 +158,14 @@ export async function loadConfig(path, options = {}) {
|
|
|
137
158
|
const file = options.strict ? File.parse(json) : json;
|
|
138
159
|
configFiles.set(path, file);
|
|
139
160
|
setConfig(file);
|
|
161
|
+
output.debug('Loaded config: ' + path);
|
|
140
162
|
for (const include of file.include ?? [])
|
|
141
163
|
await loadConfig(join(dirname(path), include), { optional: true });
|
|
142
164
|
for (const plugin of file.plugins ?? [])
|
|
143
165
|
await loadPlugin(plugin.startsWith('.') ? resolve(dirname(path), plugin) : plugin);
|
|
144
166
|
}
|
|
145
167
|
export async function loadDefaultConfigs() {
|
|
146
|
-
for (const path of
|
|
168
|
+
for (const path of findConfigPaths()) {
|
|
147
169
|
if (!existsSync(path))
|
|
148
170
|
writeFileSync(path, '{}');
|
|
149
171
|
await loadConfig(path, { optional: true });
|
|
@@ -153,7 +175,7 @@ export async function loadDefaultConfigs() {
|
|
|
153
175
|
* Update the current config and write the updated config to the appropriate file
|
|
154
176
|
*/
|
|
155
177
|
export function saveConfig(changed, global = false) {
|
|
156
|
-
saveConfigTo(
|
|
178
|
+
saveConfigTo(findConfigPaths().at(global ? 0 : -1), changed);
|
|
157
179
|
}
|
|
158
180
|
/**
|
|
159
181
|
* Update the current config and write the updated config to the provided path
|
|
@@ -168,10 +190,11 @@ export function saveConfigTo(path, changed) {
|
|
|
168
190
|
/**
|
|
169
191
|
* Find the path to the config file
|
|
170
192
|
*/
|
|
171
|
-
export function
|
|
193
|
+
export function findConfigPaths() {
|
|
194
|
+
const paths = dirs.map(dir => join(dir, 'config.json'));
|
|
172
195
|
if (process.env.AXIUM_CONFIG)
|
|
173
|
-
|
|
174
|
-
return
|
|
196
|
+
paths.push(process.env.AXIUM_CONFIG);
|
|
197
|
+
return paths;
|
|
175
198
|
}
|
|
176
199
|
if (process.env.AXIUM_CONFIG)
|
|
177
200
|
await loadConfig(process.env.AXIUM_CONFIG);
|
package/dist/database.js
CHANGED
|
@@ -57,10 +57,13 @@ import pg from 'pg';
|
|
|
57
57
|
import config from './config.js';
|
|
58
58
|
import { _fixOutput, run, someWarnings } from './io.js';
|
|
59
59
|
import { plugins } from './plugins.js';
|
|
60
|
+
const sym = Symbol.for('Axium:database');
|
|
60
61
|
export let database;
|
|
61
62
|
export function connect() {
|
|
62
63
|
if (database)
|
|
63
64
|
return database;
|
|
65
|
+
if (globalThis[sym])
|
|
66
|
+
return (database = globalThis[sym]);
|
|
64
67
|
const _db = new Kysely({
|
|
65
68
|
dialect: new PostgresDialect({ pool: new pg.Pool(config.db) }),
|
|
66
69
|
});
|
|
@@ -69,6 +72,7 @@ export function connect() {
|
|
|
69
72
|
await _db.destroy();
|
|
70
73
|
},
|
|
71
74
|
});
|
|
75
|
+
globalThis[sym] = database;
|
|
72
76
|
return database;
|
|
73
77
|
}
|
|
74
78
|
export async function count(table) {
|
package/dist/io.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Logger } from 'logzen';
|
|
2
|
+
export declare const systemDir = "/etc/axium";
|
|
3
|
+
export declare const userDir: string;
|
|
4
|
+
export declare const dirs: string[];
|
|
5
|
+
export declare const logger: Logger;
|
|
2
6
|
/**
|
|
3
|
-
*
|
|
4
|
-
* This directory includes things like config files, secrets, etc.
|
|
7
|
+
* @internal
|
|
5
8
|
*/
|
|
6
|
-
export declare function findDir(global: boolean): string;
|
|
7
|
-
export declare const logger: Logger;
|
|
8
9
|
export declare const output: {
|
|
9
10
|
constructor: {
|
|
10
11
|
name: string;
|
|
@@ -36,6 +37,7 @@ export interface MaybeOutput {
|
|
|
36
37
|
export interface WithOutput {
|
|
37
38
|
output: Output;
|
|
38
39
|
}
|
|
40
|
+
export declare function _setDebugOutput(enabled: boolean): void;
|
|
39
41
|
export declare function defaultOutput(tag: 'done'): void;
|
|
40
42
|
export declare function defaultOutput(tag: Exclude<OutputTag, 'done'>, message: string): void;
|
|
41
43
|
/**
|
package/dist/io.js
CHANGED
|
@@ -2,29 +2,32 @@ import { Logger } from 'logzen';
|
|
|
2
2
|
import { exec } from 'node:child_process';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path/posix';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path/posix';
|
|
6
6
|
import { styleText } from 'node:util';
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
import { _unique } from './state.js';
|
|
8
|
+
export const systemDir = '/etc/axium';
|
|
9
|
+
export const userDir = join(homedir(), '.axium');
|
|
10
|
+
export const dirs = _unique('dirs', [systemDir, userDir]);
|
|
11
|
+
for (let dir = resolve(process.cwd()); dir !== '/'; dir = dirname(dir)) {
|
|
12
|
+
if (fs.existsSync(join(dir, '.axium')))
|
|
13
|
+
dirs.push(join(dir, '.axium'));
|
|
14
|
+
}
|
|
15
|
+
if (process.env.AXIUM_DIR)
|
|
16
|
+
dirs.push(process.env.AXIUM_DIR);
|
|
17
|
+
try {
|
|
18
|
+
fs.mkdirSync(systemDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Missing permissions
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
fs.mkdirSync('/etc/axium', { recursive: true });
|
|
23
|
-
fs.mkdirSync(findDir(false), { recursive: true });
|
|
23
|
+
fs.mkdirSync(userDir, { recursive: true });
|
|
24
24
|
export const logger = new Logger({
|
|
25
25
|
hideWarningStack: true,
|
|
26
26
|
noGlobalConsole: true,
|
|
27
27
|
});
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
28
31
|
export const output = {
|
|
29
32
|
constructor: { name: 'Console' },
|
|
30
33
|
error(message) {
|
|
@@ -40,7 +43,7 @@ export const output = {
|
|
|
40
43
|
console.log(message);
|
|
41
44
|
},
|
|
42
45
|
debug(message) {
|
|
43
|
-
console.debug(message.startsWith('\x1b') ? message : styleText('gray', message));
|
|
46
|
+
_debugOutput && console.debug(message.startsWith('\x1b') ? message : styleText('gray', message));
|
|
44
47
|
},
|
|
45
48
|
};
|
|
46
49
|
logger.attach(output);
|
|
@@ -85,10 +88,14 @@ export function handleError(e) {
|
|
|
85
88
|
else
|
|
86
89
|
exit(e);
|
|
87
90
|
}
|
|
91
|
+
let _debugOutput = false;
|
|
92
|
+
export function _setDebugOutput(enabled) {
|
|
93
|
+
_debugOutput = enabled;
|
|
94
|
+
}
|
|
88
95
|
export function defaultOutput(tag, message = '') {
|
|
89
96
|
switch (tag) {
|
|
90
97
|
case 'debug':
|
|
91
|
-
|
|
98
|
+
_debugOutput && output.debug(message);
|
|
92
99
|
break;
|
|
93
100
|
case 'info':
|
|
94
101
|
console.log(message);
|
package/dist/plugins.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import z from 'zod/v4';
|
|
2
2
|
export declare const fn: z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>;
|
|
3
3
|
export declare const Plugin: z.ZodObject<{
|
|
4
|
-
id: z.ZodString;
|
|
5
4
|
name: z.ZodString;
|
|
6
5
|
version: z.ZodString;
|
|
7
6
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -11,11 +10,14 @@ export declare const Plugin: z.ZodObject<{
|
|
|
11
10
|
db_wipe: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
|
|
12
11
|
db_clean: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
|
|
13
12
|
}, z.core.$strip>;
|
|
13
|
+
declare const kSpecifier: unique symbol;
|
|
14
14
|
export interface Plugin extends z.infer<typeof Plugin> {
|
|
15
|
+
[kSpecifier]: string;
|
|
15
16
|
}
|
|
16
17
|
export declare const plugins: Set<Plugin>;
|
|
17
18
|
export declare function resolvePlugin(search: string): Plugin | undefined;
|
|
18
19
|
export declare function pluginText(plugin: Plugin): string;
|
|
19
20
|
export declare function loadPlugin(specifier: string): Promise<void>;
|
|
21
|
+
export declare function getSpecifier(plugin: Plugin): string;
|
|
20
22
|
export declare function loadPlugins(dir: string): Promise<void>;
|
|
21
|
-
export
|
|
23
|
+
export {};
|
package/dist/plugins.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { zAsyncFunction } from '@axium/core/schemas';
|
|
1
2
|
import * as fs from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
+
import { resolve } from 'node:path/posix';
|
|
3
4
|
import { styleText } from 'node:util';
|
|
4
5
|
import z from 'zod/v4';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { output } from './io.js';
|
|
7
|
+
import { _unique } from './state.js';
|
|
7
8
|
export const fn = z.custom(data => typeof data === 'function');
|
|
8
9
|
export const Plugin = z.object({
|
|
9
|
-
id: z.string(),
|
|
10
10
|
name: z.string(),
|
|
11
11
|
version: z.string(),
|
|
12
12
|
description: z.string().optional(),
|
|
@@ -16,17 +16,17 @@ export const Plugin = z.object({
|
|
|
16
16
|
db_wipe: fn.optional(),
|
|
17
17
|
db_clean: fn.optional(),
|
|
18
18
|
});
|
|
19
|
-
|
|
19
|
+
const kSpecifier = Symbol('specifier');
|
|
20
|
+
export const plugins = _unique('plugins', new Set());
|
|
20
21
|
export function resolvePlugin(search) {
|
|
21
22
|
for (const plugin of plugins) {
|
|
22
|
-
if (plugin.name.startsWith(search)
|
|
23
|
+
if (plugin.name.startsWith(search))
|
|
23
24
|
return plugin;
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
export function pluginText(plugin) {
|
|
27
28
|
return [
|
|
28
29
|
styleText('whiteBright', plugin.name),
|
|
29
|
-
plugin.id,
|
|
30
30
|
`Version: ${plugin.version}`,
|
|
31
31
|
`Description: ${plugin.description ?? styleText('dim', '(none)')}`,
|
|
32
32
|
`Database integration: ${[plugin.db_init, plugin.db_remove, plugin.db_wipe]
|
|
@@ -37,16 +37,21 @@ export function pluginText(plugin) {
|
|
|
37
37
|
}
|
|
38
38
|
export async function loadPlugin(specifier) {
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
40
|
+
const imported = await import(/* @vite-ignore */ specifier);
|
|
41
|
+
const maybePlugin = 'default' in imported ? imported.default : imported;
|
|
42
|
+
const plugin = Object.assign(await Plugin.parseAsync(maybePlugin).catch(e => {
|
|
41
43
|
throw z.prettifyError(e);
|
|
42
|
-
});
|
|
44
|
+
}), { [kSpecifier]: specifier });
|
|
43
45
|
plugins.add(plugin);
|
|
44
|
-
output.debug(`Loaded plugin:
|
|
46
|
+
output.debug(`Loaded plugin: ${plugin.name} ${plugin.version}`);
|
|
45
47
|
}
|
|
46
48
|
catch (e) {
|
|
47
49
|
output.debug(`Failed to load plugin from ${specifier}: ${e.message || e}`);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
52
|
+
export function getSpecifier(plugin) {
|
|
53
|
+
return plugin[kSpecifier];
|
|
54
|
+
}
|
|
50
55
|
export async function loadPlugins(dir) {
|
|
51
56
|
fs.mkdirSync(dir, { recursive: true });
|
|
52
57
|
const files = fs.readdirSync(dir);
|
|
@@ -58,7 +63,3 @@ export async function loadPlugins(dir) {
|
|
|
58
63
|
await loadPlugin(path);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
|
-
export async function loadDefaultPlugins() {
|
|
62
|
-
await loadPlugins(join(findDir(true), 'plugins'));
|
|
63
|
-
await loadPlugins(join(findDir(false), 'plugins'));
|
|
64
|
-
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NewSessionResponse } from '@axium/core/api';
|
|
2
|
+
import { type User } from '@axium/core/user';
|
|
3
|
+
import { type HttpError, type RequestEvent } from '@sveltejs/kit';
|
|
4
|
+
import z from 'zod/v4';
|
|
5
|
+
import { type SessionAndUser, type UserInternal } from './auth.js';
|
|
6
|
+
export declare function parseBody<const Schema extends z.ZodType, const Result extends z.infer<Schema> = z.infer<Schema>>(event: RequestEvent, schema: Schema): Promise<Result>;
|
|
7
|
+
export declare function getToken(event: RequestEvent, sensitive?: boolean): string | undefined;
|
|
8
|
+
export declare function checkAuth(event: RequestEvent, userId: string, sensitive?: boolean): Promise<SessionAndUser>;
|
|
9
|
+
export declare function createSessionData(event: RequestEvent, userId: string, elevated?: boolean): Promise<NewSessionResponse>;
|
|
10
|
+
export declare function stripUser(user: UserInternal, includeProtected?: boolean): User;
|
|
11
|
+
export declare function withError(text: string, code?: number): (e: Error | HttpError) => never;
|
package/dist/requests.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { userProtectedFields, userPublicFields } from '@axium/core/user';
|
|
2
|
+
import { error } from '@sveltejs/kit';
|
|
3
|
+
import { pick } from 'utilium';
|
|
4
|
+
import z from 'zod/v4';
|
|
5
|
+
import { createSession, getSessionAndUser } from './auth.js';
|
|
6
|
+
import { config } from './config.js';
|
|
7
|
+
export async function parseBody(event, schema) {
|
|
8
|
+
const contentType = event.request.headers.get('content-type');
|
|
9
|
+
if (!contentType || !contentType.includes('application/json'))
|
|
10
|
+
error(415, { message: 'Invalid content type' });
|
|
11
|
+
const body = await event.request.json().catch(() => error(415, { message: 'Invalid JSON' }));
|
|
12
|
+
try {
|
|
13
|
+
return schema.parse(body);
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
error(400, { message: z.prettifyError(e) });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function getToken(event, sensitive = false) {
|
|
20
|
+
const header_token = event.request.headers.get('Authorization')?.replace('Bearer ', '');
|
|
21
|
+
if (header_token)
|
|
22
|
+
return header_token;
|
|
23
|
+
if (config.debug || config.api.cookie_auth) {
|
|
24
|
+
return event.cookies.get(sensitive ? 'elevated_token' : 'session_token');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function checkAuth(event, userId, sensitive = false) {
|
|
28
|
+
const token = getToken(event, sensitive);
|
|
29
|
+
if (!token)
|
|
30
|
+
throw error(401, { message: 'Missing token' });
|
|
31
|
+
const session = await getSessionAndUser(token).catch(() => error(401, { message: 'Invalid or expired session' }));
|
|
32
|
+
if (session.user?.id !== userId /* && !user.isAdmin */)
|
|
33
|
+
error(403, { message: 'User ID mismatch' });
|
|
34
|
+
if (!session.elevated && sensitive)
|
|
35
|
+
error(403, 'This token can not be used for sensitive actions');
|
|
36
|
+
return session;
|
|
37
|
+
}
|
|
38
|
+
export async function createSessionData(event, userId, elevated = false) {
|
|
39
|
+
const { token } = await createSession(userId, elevated);
|
|
40
|
+
if (elevated) {
|
|
41
|
+
event.cookies.set('elevated_token', token, { httpOnly: true, path: '/', expires: new Date(Date.now() + 10 * 60_000) });
|
|
42
|
+
return { userId, token: '[[redacted:elevated]]' };
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
event.cookies.set('session_token', token, { httpOnly: config.auth.secure_cookies, path: '/' });
|
|
46
|
+
return { userId, token };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function stripUser(user, includeProtected = false) {
|
|
50
|
+
return pick(user, ...userPublicFields, ...(includeProtected ? userProtectedFields : []));
|
|
51
|
+
}
|
|
52
|
+
export function withError(text, code = 500) {
|
|
53
|
+
return function (e) {
|
|
54
|
+
if ('body' in e)
|
|
55
|
+
throw e;
|
|
56
|
+
error(code, { message: text + (config.debug && e.message ? `: ${e.message}` : '') });
|
|
57
|
+
};
|
|
58
|
+
}
|