@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.
Files changed (70) hide show
  1. package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
  2. package/dist/api/index.js +5 -0
  3. package/dist/api/metadata.d.ts +1 -0
  4. package/dist/api/metadata.js +28 -0
  5. package/dist/api/passkeys.d.ts +1 -0
  6. package/dist/api/passkeys.js +50 -0
  7. package/dist/api/register.d.ts +1 -0
  8. package/dist/api/register.js +70 -0
  9. package/dist/api/session.d.ts +1 -0
  10. package/dist/api/session.js +31 -0
  11. package/dist/api/users.d.ts +1 -0
  12. package/dist/api/users.js +244 -0
  13. package/dist/apps.d.ts +0 -5
  14. package/dist/apps.js +2 -9
  15. package/dist/auth.d.ts +9 -21
  16. package/dist/auth.js +12 -18
  17. package/dist/cli.js +65 -26
  18. package/dist/config.d.ts +15 -8
  19. package/dist/config.js +38 -15
  20. package/dist/database.js +4 -0
  21. package/dist/io.d.ts +6 -4
  22. package/dist/io.js +26 -19
  23. package/dist/plugins.d.ts +4 -2
  24. package/dist/plugins.js +15 -14
  25. package/dist/requests.d.ts +11 -0
  26. package/dist/requests.js +58 -0
  27. package/dist/routes.d.ts +12 -13
  28. package/dist/routes.js +21 -22
  29. package/dist/serve.d.ts +7 -0
  30. package/dist/serve.js +11 -0
  31. package/dist/state.d.ts +4 -0
  32. package/dist/state.js +22 -0
  33. package/dist/sveltekit.d.ts +8 -0
  34. package/dist/sveltekit.js +90 -0
  35. package/package.json +10 -5
  36. package/svelte.config.js +36 -0
  37. package/web/hooks.server.ts +8 -3
  38. package/web/lib/Dialog.svelte +0 -1
  39. package/web/lib/FormDialog.svelte +0 -1
  40. package/web/lib/icons/Icon.svelte +2 -7
  41. package/web/template.html +18 -0
  42. package/web/tsconfig.json +3 -2
  43. package/web/api/metadata.ts +0 -35
  44. package/web/api/passkeys.ts +0 -56
  45. package/web/api/readme.md +0 -1
  46. package/web/api/register.ts +0 -83
  47. package/web/api/schemas.ts +0 -22
  48. package/web/api/session.ts +0 -33
  49. package/web/api/users.ts +0 -351
  50. package/web/api/utils.ts +0 -66
  51. package/web/app.html +0 -14
  52. package/web/auth.ts +0 -8
  53. package/web/index.server.ts +0 -1
  54. package/web/index.ts +0 -1
  55. package/web/lib/auth.ts +0 -12
  56. package/web/lib/index.ts +0 -5
  57. package/web/routes/+layout.svelte +0 -6
  58. package/web/routes/[...path]/+page.server.ts +0 -13
  59. package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
  60. package/web/routes/api/[...path]/+server.ts +0 -49
  61. package/web/utils.ts +0 -26
  62. /package/{web/lib → assets}/icons/light.svg +0 -0
  63. /package/{web/lib → assets}/icons/regular.svg +0 -0
  64. /package/{web/lib → assets}/icons/solid.svg +0 -0
  65. /package/{web/lib → assets}/styles.css +0 -0
  66. /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
  67. /package/{web/routes → routes}/account/+page.svelte +0 -0
  68. /package/{web/routes → routes}/login/+page.svelte +0 -0
  69. /package/{web/routes → routes}/logout/+page.svelte +0 -0
  70. /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, styleText('dim', `(${plugin.id})`), opt.versions ? plugin.version : '');
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 env_4 = { stack: [], error: void 0, hasError: false };
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(env_4, db.connect(), true);
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.id)
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 (e_4) {
347
- env_4.error = e_4;
348
- env_4.hasError = true;
370
+ catch (e_5) {
371
+ env_5.error = e_5;
372
+ env_5.hasError = true;
349
373
  }
350
374
  finally {
351
- const result_4 = __disposeResources(env_4);
352
- if (result_4)
353
- await result_4;
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, PartialRecursive<Config>>;
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 findConfigPath;
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, PartialRecursive<Config>>;
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 findConfigPath(global: boolean): string;
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 { findDir, logger, output } from './io.js';
6
+ import { _setDebugOutput, dirs, logger, output } from './io.js';
7
7
  import { loadPlugin } from './plugins.js';
8
- export const configFiles = new Map();
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: findConfigPath,
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: false,
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 [findConfigPath(true), findConfigPath(false)]) {
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(process.env.AXIUM_CONFIG ?? findConfigPath(global), changed);
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 findConfigPath(global) {
193
+ export function findConfigPaths() {
194
+ const paths = dirs.map(dir => join(dir, 'config.json'));
172
195
  if (process.env.AXIUM_CONFIG)
173
- return process.env.AXIUM_CONFIG;
174
- return join(findDir(global), 'config.json');
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
- * Find the Axium directory.
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 config from './config.js';
8
- /**
9
- * Find the Axium directory.
10
- * This directory includes things like config files, secrets, etc.
11
- */
12
- export function findDir(global) {
13
- if (process.env.AXIUM_DIR)
14
- return process.env.AXIUM_DIR;
15
- if (global && process.getuid?.() === 0)
16
- return '/etc/axium';
17
- if (global)
18
- return join(homedir(), '.axium');
19
- return '.axium';
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
- if (process.getuid?.() === 0)
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
- config.debug && output.debug(message);
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 declare function loadDefaultPlugins(): Promise<void>;
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 { join, resolve } from 'node:path/posix';
3
+ import { resolve } from 'node:path/posix';
3
4
  import { styleText } from 'node:util';
4
5
  import z from 'zod/v4';
5
- import { findDir, output } from './io.js';
6
- import { zAsyncFunction } from '@axium/core/schemas';
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
- export const plugins = new Set();
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) || plugin.id.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 plugin = await Plugin.parseAsync(await import(/* @vite-ignore */ specifier)).catch(e => {
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: "${plugin.name}" (${plugin.id}) ${plugin.version}`);
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;
@@ -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
+ }