@axium/server 0.8.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 (72) hide show
  1. package/{web/lib → assets}/styles.css +7 -1
  2. package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
  3. package/dist/api/index.js +5 -0
  4. package/dist/api/metadata.d.ts +1 -0
  5. package/dist/api/metadata.js +28 -0
  6. package/dist/api/passkeys.d.ts +1 -0
  7. package/dist/api/passkeys.js +50 -0
  8. package/dist/api/register.d.ts +1 -0
  9. package/dist/api/register.js +70 -0
  10. package/dist/api/session.d.ts +1 -0
  11. package/dist/api/session.js +31 -0
  12. package/dist/api/users.d.ts +1 -0
  13. package/dist/api/users.js +244 -0
  14. package/dist/apps.d.ts +0 -5
  15. package/dist/apps.js +2 -9
  16. package/dist/auth.d.ts +9 -31
  17. package/dist/auth.js +20 -34
  18. package/dist/cli.js +200 -54
  19. package/dist/config.d.ts +52 -480
  20. package/dist/config.js +89 -56
  21. package/dist/database.d.ts +3 -3
  22. package/dist/database.js +57 -24
  23. package/dist/io.d.ts +6 -4
  24. package/dist/io.js +26 -19
  25. package/dist/plugins.d.ts +5 -2
  26. package/dist/plugins.js +16 -14
  27. package/dist/requests.d.ts +11 -0
  28. package/dist/requests.js +58 -0
  29. package/dist/routes.d.ts +12 -13
  30. package/dist/routes.js +21 -22
  31. package/dist/serve.d.ts +7 -0
  32. package/dist/serve.js +11 -0
  33. package/dist/state.d.ts +4 -0
  34. package/dist/state.js +22 -0
  35. package/dist/sveltekit.d.ts +8 -0
  36. package/dist/sveltekit.js +90 -0
  37. package/package.json +18 -10
  38. package/{web/routes → routes}/account/+page.svelte +115 -48
  39. package/svelte.config.js +36 -0
  40. package/web/hooks.server.ts +15 -4
  41. package/web/lib/ClipboardCopy.svelte +42 -0
  42. package/web/lib/Dialog.svelte +0 -1
  43. package/web/lib/FormDialog.svelte +9 -2
  44. package/web/lib/icons/Icon.svelte +3 -12
  45. package/web/template.html +18 -0
  46. package/web/tsconfig.json +3 -2
  47. package/web/api/metadata.ts +0 -35
  48. package/web/api/passkeys.ts +0 -56
  49. package/web/api/readme.md +0 -1
  50. package/web/api/register.ts +0 -83
  51. package/web/api/schemas.ts +0 -22
  52. package/web/api/session.ts +0 -33
  53. package/web/api/users.ts +0 -340
  54. package/web/api/utils.ts +0 -66
  55. package/web/app.html +0 -14
  56. package/web/auth.ts +0 -8
  57. package/web/index.server.ts +0 -1
  58. package/web/index.ts +0 -1
  59. package/web/lib/auth.ts +0 -12
  60. package/web/lib/index.ts +0 -5
  61. package/web/routes/+layout.svelte +0 -6
  62. package/web/routes/[...path]/+page.server.ts +0 -13
  63. package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
  64. package/web/routes/api/[...path]/+server.ts +0 -49
  65. package/web/utils.ts +0 -26
  66. /package/{web/lib → assets}/icons/light.svg +0 -0
  67. /package/{web/lib → assets}/icons/regular.svg +0 -0
  68. /package/{web/lib → assets}/icons/solid.svg +0 -0
  69. /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
  70. /package/{web/routes → routes}/login/+page.svelte +0 -0
  71. /package/{web/routes → routes}/logout/+page.svelte +0 -0
  72. /package/{web/routes → routes}/register/+page.svelte +0 -0
package/dist/auth.js CHANGED
@@ -3,17 +3,7 @@ import { randomBytes, randomUUID } from 'node:crypto';
3
3
  import { connect, database as db } from './database.js';
4
4
  export async function getUser(id) {
5
5
  connect();
6
- const result = await db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst();
7
- if (!result)
8
- return null;
9
- return result;
10
- }
11
- export async function getUserByEmail(email) {
12
- connect();
13
- const result = await db.selectFrom('users').selectAll().where('email', '=', email).executeTakeFirst();
14
- if (!result)
15
- return null;
16
- return result;
6
+ return await db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirstOrThrow();
17
7
  }
18
8
  export async function updateUser({ id, ...user }) {
19
9
  connect();
@@ -35,12 +25,6 @@ export async function createSession(userId, elevated = false) {
35
25
  await db.insertInto('sessions').values(session).execute();
36
26
  return session;
37
27
  }
38
- export async function checkExpiration(session) {
39
- if (session.expires.getTime() > Date.now())
40
- return;
41
- await db.deleteFrom('sessions').where('sessions.id', '=', session.id).executeTakeFirstOrThrow();
42
- throw new Error('Session expired');
43
- }
44
28
  export async function getSessionAndUser(token) {
45
29
  connect();
46
30
  const result = await db
@@ -48,26 +32,24 @@ export async function getSessionAndUser(token) {
48
32
  .selectAll()
49
33
  .select(eb => jsonObjectFrom(eb.selectFrom('users').selectAll().whereRef('users.id', '=', 'sessions.userId')).as('user'))
50
34
  .where('sessions.token', '=', token)
51
- .executeTakeFirst();
52
- if (!result)
53
- throw new Error('Session not found');
54
- await checkExpiration(result);
35
+ .where('sessions.expires', '>', new Date())
36
+ .executeTakeFirstOrThrow();
37
+ if (!result.user)
38
+ throw new Error('Session references non-existing user');
55
39
  return result;
56
40
  }
57
41
  export async function getSession(sessionId) {
58
42
  connect();
59
- const session = await db.selectFrom('sessions').selectAll().where('id', '=', sessionId).executeTakeFirstOrThrow();
60
- await checkExpiration(session);
61
- return session;
43
+ return await db
44
+ .selectFrom('sessions')
45
+ .selectAll()
46
+ .where('id', '=', sessionId)
47
+ .where('sessions.expires', '>', new Date())
48
+ .executeTakeFirstOrThrow();
62
49
  }
63
50
  export async function getSessions(userId) {
64
51
  connect();
65
- return await db.selectFrom('sessions').selectAll().where('userId', '=', userId).execute();
66
- }
67
- export async function updateSession(session) {
68
- connect();
69
- const query = db.updateTable('sessions').set(session).where('sessions.token', '=', session.token);
70
- return await query.returningAll().executeTakeFirstOrThrow();
52
+ return await db.selectFrom('sessions').selectAll().where('userId', '=', userId).where('sessions.expires', '>', new Date()).execute();
71
53
  }
72
54
  /**
73
55
  * Create a verification
@@ -94,10 +76,7 @@ export async function useVerification(role, userId, token) {
94
76
  }
95
77
  export async function getPasskey(id) {
96
78
  connect();
97
- const result = await db.selectFrom('passkeys').selectAll().where('id', '=', id).executeTakeFirst();
98
- if (!result)
99
- return null;
100
- return result;
79
+ return await db.selectFrom('passkeys').selectAll().where('id', '=', id).executeTakeFirstOrThrow();
101
80
  }
102
81
  export async function createPasskey(passkey) {
103
82
  connect();
@@ -116,3 +95,10 @@ export async function updatePasskeyCounter(id, newCounter) {
116
95
  throw new Error('Passkey not found');
117
96
  return passkey;
118
97
  }
98
+ export async function authenticate(event) {
99
+ const maybe_header = event.request.headers.get('Authorization');
100
+ const token = maybe_header?.startsWith('Bearer ') ? maybe_header.slice(7) : event.cookies.get('session_token');
101
+ if (!token)
102
+ return null;
103
+ return await getSessionAndUser(token).catch(() => null);
104
+ }
package/dist/cli.js CHANGED
@@ -1,13 +1,67 @@
1
1
  #!/usr/bin/env node
2
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
3
+ if (value !== null && value !== void 0) {
4
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
5
+ var dispose, inner;
6
+ if (async) {
7
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
8
+ dispose = value[Symbol.asyncDispose];
9
+ }
10
+ if (dispose === void 0) {
11
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
12
+ dispose = value[Symbol.dispose];
13
+ if (async) inner = dispose;
14
+ }
15
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
16
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
17
+ env.stack.push({ value: value, dispose: dispose, async: async });
18
+ }
19
+ else if (async) {
20
+ env.stack.push({ async: true });
21
+ }
22
+ return value;
23
+ };
24
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
25
+ return function (env) {
26
+ function fail(e) {
27
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
28
+ env.hasError = true;
29
+ }
30
+ var r, s = 0;
31
+ function next() {
32
+ while (r = env.stack.pop()) {
33
+ try {
34
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
35
+ if (r.dispose) {
36
+ var result = r.dispose.call(r.value);
37
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
38
+ }
39
+ else s |= 1;
40
+ }
41
+ catch (e) {
42
+ fail(e);
43
+ }
44
+ }
45
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
46
+ if (env.hasError) throw env.error;
47
+ }
48
+ return next();
49
+ };
50
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
51
+ var e = new Error(message);
52
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
+ });
2
54
  import { Argument, Option, program } from 'commander';
55
+ import { join } from 'node:path/posix';
3
56
  import { styleText } from 'node:util';
4
57
  import { getByString, isJSON, setByString } from 'utilium';
5
58
  import $pkg from '../package.json' with { type: 'json' };
6
- import config from './config.js';
7
- import * as db from './database.js';
8
- import { _portActions, _portMethods, exit, handleError, output, restrictedPorts } from './io.js';
9
- import { loadDefaultPlugins, plugins, pluginText, resolvePlugin } from './plugins.js';
10
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';
11
65
  program
12
66
  .version($pkg.version)
13
67
  .name('axium')
@@ -20,15 +74,10 @@ program.on('option:debug', () => config.set({ debug: true }));
20
74
  program.on('option:config', () => void config.load(program.opts().config));
21
75
  program.hook('preAction', async function (_, action) {
22
76
  await config.loadDefaults();
23
- await loadDefaultPlugins();
24
77
  const opt = action.optsWithGlobals();
25
78
  opt.force && output.warn('--force: Protections disabled.');
26
79
  if (opt.debug === false)
27
80
  config.set({ debug: false });
28
- /* if (!config.auth.secret) {
29
- config.save({ auth: { secret: process.env.AUTH_SECRET || randomBytes(32).toString('base64') } }, true);
30
- output.debug('Auto-generated a new auth secret');
31
- } */
32
81
  });
33
82
  // Options shared by multiple (sub)commands
34
83
  const opts = {
@@ -76,32 +125,56 @@ axiumDB
76
125
  .description('Drop the Axium database and user')
77
126
  .addOption(opts.force)
78
127
  .action(async (opt) => {
79
- const stats = await db.status().catch(exit);
80
- if (!opt.force)
81
- for (const key of ['users', 'passkeys', 'sessions']) {
82
- if (stats[key] == 0)
83
- continue;
84
- output.warn(`Database has existing ${key}. Use --force if you really want to drop the database.`);
85
- process.exit(2);
86
- }
87
- await db.uninstall(opt).catch(exit);
88
- await db.database.destroy();
128
+ const env_1 = { stack: [], error: void 0, hasError: false };
129
+ try {
130
+ const _ = __addDisposableResource(env_1, db.connect(), true);
131
+ const stats = await db.status().catch(exit);
132
+ if (!opt.force)
133
+ for (const key of ['users', 'passkeys', 'sessions']) {
134
+ if (stats[key] == 0)
135
+ continue;
136
+ output.warn(`Database has existing ${key}. Use --force if you really want to drop the database.`);
137
+ process.exit(2);
138
+ }
139
+ await db.uninstall(opt).catch(exit);
140
+ }
141
+ catch (e_1) {
142
+ env_1.error = e_1;
143
+ env_1.hasError = true;
144
+ }
145
+ finally {
146
+ const result_1 = __disposeResources(env_1);
147
+ if (result_1)
148
+ await result_1;
149
+ }
89
150
  });
90
151
  axiumDB
91
152
  .command('wipe')
92
153
  .description('Wipe the database')
93
154
  .addOption(opts.force)
94
155
  .action(async (opt) => {
95
- const stats = await db.status().catch(exit);
96
- if (!opt.force)
97
- for (const key of ['users', 'passkeys', 'sessions']) {
98
- if (stats[key] == 0)
99
- continue;
100
- output.warn(`Database has existing ${key}. Use --force if you really want to wipe the database.`);
101
- process.exit(2);
102
- }
103
- await db.wipe(opt).catch(exit);
104
- await db.database.destroy();
156
+ const env_2 = { stack: [], error: void 0, hasError: false };
157
+ try {
158
+ const _ = __addDisposableResource(env_2, db.connect(), true);
159
+ const stats = await db.status().catch(exit);
160
+ if (!opt.force)
161
+ for (const key of ['users', 'passkeys', 'sessions']) {
162
+ if (stats[key] == 0)
163
+ continue;
164
+ output.warn(`Database has existing ${key}. Use --force if you really want to wipe the database.`);
165
+ process.exit(2);
166
+ }
167
+ await db.wipe(opt).catch(exit);
168
+ }
169
+ catch (e_2) {
170
+ env_2.error = e_2;
171
+ env_2.hasError = true;
172
+ }
173
+ finally {
174
+ const result_2 = __disposeResources(env_2);
175
+ if (result_2)
176
+ await result_2;
177
+ }
105
178
  });
106
179
  axiumDB
107
180
  .command('check')
@@ -109,6 +182,26 @@ axiumDB
109
182
  .action(async (opt) => {
110
183
  await db.check(opt).catch(exit);
111
184
  });
185
+ axiumDB
186
+ .command('clean')
187
+ .description('Remove expired rows')
188
+ .addOption(opts.force)
189
+ .action(async (opt) => {
190
+ const env_3 = { stack: [], error: void 0, hasError: false };
191
+ try {
192
+ const _ = __addDisposableResource(env_3, db.connect(), true);
193
+ await db.clean(opt).catch(exit);
194
+ }
195
+ catch (e_3) {
196
+ env_3.error = e_3;
197
+ env_3.hasError = true;
198
+ }
199
+ finally {
200
+ const result_3 = __disposeResources(env_3);
201
+ if (result_3)
202
+ await result_3;
203
+ }
204
+ });
112
205
  const axiumConfig = program
113
206
  .command('config')
114
207
  .description('Manage the configuration')
@@ -117,7 +210,6 @@ const axiumConfig = program
117
210
  .option('-r, --redact', 'Do not output sensitive values');
118
211
  function configReplacer(opt) {
119
212
  return (key, value) => {
120
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
121
213
  return opt.redact && ['password', 'secret'].includes(key) ? '[redacted]' : value;
122
214
  };
123
215
  }
@@ -160,11 +252,7 @@ axiumConfig
160
252
  for (const path of config.files.keys())
161
253
  console.log(path);
162
254
  });
163
- const axiumPlugin = program
164
- .command('plugins')
165
- .description('Manage plugins')
166
- .addOption(opts.global)
167
- .option('--safe', 'do not perform actions that would execute code from plugins.');
255
+ const axiumPlugin = program.command('plugins').description('Manage plugins').addOption(opts.global);
168
256
  axiumPlugin
169
257
  .command('list')
170
258
  .alias('ls')
@@ -184,7 +272,7 @@ axiumPlugin
184
272
  }
185
273
  console.log(styleText('whiteBright', plugins.size + ' plugin(s) loaded:'));
186
274
  for (const plugin of plugins) {
187
- console.log(plugin.name, styleText('dim', `(${plugin.id})`), opt.versions ? plugin.version : '');
275
+ console.log(plugin.name, opt.versions ? plugin.version : '');
188
276
  }
189
277
  });
190
278
  axiumPlugin
@@ -197,6 +285,38 @@ axiumPlugin
197
285
  exit(`Can't find a plugin matching "${search}"`);
198
286
  console.log(pluginText(plugin));
199
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
+ });
200
320
  const axiumApps = program.command('apps').description('Manage Axium apps').addOption(opts.global);
201
321
  axiumApps
202
322
  .command('list')
@@ -224,27 +344,38 @@ program
224
344
  .description('Get information about the server')
225
345
  .addOption(opts.host)
226
346
  .action(async () => {
227
- console.log('Axium Server v' + program.version());
228
- console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
229
- console.log(styleText('whiteBright', 'Loaded config files:'), config.files.keys().toArray().join(', '));
230
- process.stdout.write(styleText('whiteBright', 'Database: '));
347
+ const env_5 = { stack: [], error: void 0, hasError: false };
231
348
  try {
232
- console.log(await db.statusText());
349
+ console.log('Axium Server v' + program.version());
350
+ console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
351
+ console.log(styleText('whiteBright', 'Loaded config files:'), config.files.keys().toArray().join(', '));
352
+ process.stdout.write(styleText('whiteBright', 'Database: '));
353
+ const _ = __addDisposableResource(env_5, db.connect(), true);
354
+ try {
355
+ console.log(await db.statusText());
356
+ }
357
+ catch {
358
+ output.error('Unavailable');
359
+ }
360
+ console.log(styleText('whiteBright', 'Loaded plugins:'), Array.from(plugins)
361
+ .map(plugin => plugin.name)
362
+ .join(', ') || styleText('dim', '(none)'));
363
+ for (const plugin of plugins) {
364
+ if (!plugin.statusText)
365
+ continue;
366
+ console.log(styleText('bold', plugin.name), plugin.version + ':');
367
+ console.log(await plugin.statusText());
368
+ }
233
369
  }
234
- catch {
235
- output.error('Unavailable');
370
+ catch (e_5) {
371
+ env_5.error = e_5;
372
+ env_5.hasError = true;
236
373
  }
237
- console.log(styleText('whiteBright', 'Credentials authentication:'), config.auth.credentials ? styleText('yellow', 'enabled') : 'disabled');
238
- console.log(styleText('whiteBright', 'Loaded plugins:'), Array.from(plugins)
239
- .map(plugin => plugin.id)
240
- .join(', ') || styleText('dim', '(none)'));
241
- for (const plugin of plugins) {
242
- if (!plugin.statusText)
243
- continue;
244
- console.log(styleText('bold', plugin.name), plugin.version + ':');
245
- console.log(await plugin.statusText());
374
+ finally {
375
+ const result_5 = __disposeResources(env_5);
376
+ if (result_5)
377
+ await result_5;
246
378
  }
247
- await db.database.destroy();
248
379
  });
249
380
  program
250
381
  .command('ports')
@@ -261,8 +392,23 @@ program
261
392
  .addOption(opts.force)
262
393
  .addOption(opts.host)
263
394
  .action(async (opt) => {
264
- /* config.save({ auth: { secret: randomBytes(32).toString('base64') } }, true); */
265
395
  await db.init({ ...opt, skip: opt.dbSkip }).catch(handleError);
266
396
  await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(handleError);
267
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
+ });
268
414
  program.parse();