@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.
- package/{web/lib → assets}/styles.css +7 -1
- 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 -31
- package/dist/auth.js +20 -34
- package/dist/cli.js +200 -54
- package/dist/config.d.ts +52 -480
- package/dist/config.js +89 -56
- package/dist/database.d.ts +3 -3
- package/dist/database.js +57 -24
- package/dist/io.d.ts +6 -4
- package/dist/io.js +26 -19
- package/dist/plugins.d.ts +5 -2
- package/dist/plugins.js +16 -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 +18 -10
- package/{web/routes → routes}/account/+page.svelte +115 -48
- package/svelte.config.js +36 -0
- package/web/hooks.server.ts +15 -4
- package/web/lib/ClipboardCopy.svelte +42 -0
- package/web/lib/Dialog.svelte +0 -1
- package/web/lib/FormDialog.svelte +9 -2
- package/web/lib/icons/Icon.svelte +3 -12
- 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 -340
- 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/routes → routes}/_axium/default/+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/config.js
CHANGED
|
@@ -1,55 +1,17 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
1
|
import { levelText } from 'logzen';
|
|
3
2
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
3
|
import { dirname, join, resolve } from 'node:path/posix';
|
|
5
4
|
import { deepAssign, omit } from 'utilium';
|
|
6
|
-
import * as z from 'zod';
|
|
7
|
-
import {
|
|
5
|
+
import * as z from 'zod/v4';
|
|
6
|
+
import { _setDebugOutput, dirs, logger, output } from './io.js';
|
|
8
7
|
import { loadPlugin } from './plugins.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
api: z.object({
|
|
12
|
-
disable_metadata: z.boolean(),
|
|
13
|
-
cookie_auth: z.boolean(),
|
|
14
|
-
}),
|
|
15
|
-
apps: z.object({
|
|
16
|
-
disabled: z.array(z.string()),
|
|
17
|
-
}),
|
|
18
|
-
auth: z.object({
|
|
19
|
-
credentials: z.boolean(),
|
|
20
|
-
debug: z.boolean(),
|
|
21
|
-
origin: z.string(),
|
|
22
|
-
/** In minutes */
|
|
23
|
-
passkey_probation: z.number(),
|
|
24
|
-
rp_id: z.string(),
|
|
25
|
-
rp_name: z.string(),
|
|
26
|
-
secure_cookies: z.boolean(),
|
|
27
|
-
/** In minutes */
|
|
28
|
-
verification_timeout: z.number(),
|
|
29
|
-
}),
|
|
30
|
-
db: z.object({
|
|
31
|
-
host: z.string(),
|
|
32
|
-
port: z.number(),
|
|
33
|
-
password: z.string(),
|
|
34
|
-
user: z.string(),
|
|
35
|
-
database: z.string(),
|
|
36
|
-
}),
|
|
37
|
-
debug: z.boolean(),
|
|
38
|
-
log: z.object({
|
|
39
|
-
level: z.enum(levelText),
|
|
40
|
-
console: z.boolean(),
|
|
41
|
-
}),
|
|
42
|
-
web: z.object({
|
|
43
|
-
prefix: z.string(),
|
|
44
|
-
}),
|
|
45
|
-
})
|
|
46
|
-
.passthrough();
|
|
47
|
-
export const configFiles = new Map();
|
|
8
|
+
import { _unique } from './state.js';
|
|
9
|
+
export const configFiles = _unique('configFiles', new Map());
|
|
48
10
|
export function plainConfig() {
|
|
49
11
|
return omit(config, Object.keys(configShortcuts));
|
|
50
12
|
}
|
|
51
13
|
const configShortcuts = {
|
|
52
|
-
findPath:
|
|
14
|
+
findPath: findConfigPaths,
|
|
53
15
|
load: loadConfig,
|
|
54
16
|
loadDefaults: loadDefaultConfigs,
|
|
55
17
|
plain: plainConfig,
|
|
@@ -58,24 +20,23 @@ const configShortcuts = {
|
|
|
58
20
|
set: setConfig,
|
|
59
21
|
files: configFiles,
|
|
60
22
|
};
|
|
61
|
-
export const config = {
|
|
23
|
+
export const config = _unique('config', {
|
|
62
24
|
...configShortcuts,
|
|
63
25
|
api: {
|
|
64
26
|
disable_metadata: false,
|
|
65
|
-
cookie_auth:
|
|
27
|
+
cookie_auth: true,
|
|
66
28
|
},
|
|
67
29
|
apps: {
|
|
68
30
|
disabled: [],
|
|
69
31
|
},
|
|
70
32
|
auth: {
|
|
71
|
-
credentials: false,
|
|
72
|
-
debug: false,
|
|
73
33
|
origin: 'https://test.localhost',
|
|
74
34
|
passkey_probation: 60,
|
|
75
35
|
rp_id: 'test.localhost',
|
|
76
36
|
rp_name: 'Axium',
|
|
77
37
|
secure_cookies: true,
|
|
78
38
|
verification_timeout: 60,
|
|
39
|
+
email_verification: false,
|
|
79
40
|
},
|
|
80
41
|
db: {
|
|
81
42
|
database: process.env.PGDATABASE || 'axium',
|
|
@@ -91,16 +52,83 @@ export const config = {
|
|
|
91
52
|
},
|
|
92
53
|
web: {
|
|
93
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'),
|
|
94
60
|
},
|
|
95
|
-
};
|
|
61
|
+
});
|
|
96
62
|
export default config;
|
|
97
63
|
// config from file
|
|
98
|
-
export const File =
|
|
99
|
-
.
|
|
64
|
+
export const File = z
|
|
65
|
+
.looseObject({
|
|
66
|
+
api: z
|
|
67
|
+
.object({
|
|
68
|
+
disable_metadata: z.boolean(),
|
|
69
|
+
cookie_auth: z.boolean(),
|
|
70
|
+
})
|
|
71
|
+
.partial(),
|
|
72
|
+
apps: z
|
|
73
|
+
.object({
|
|
74
|
+
disabled: z.array(z.string()),
|
|
75
|
+
})
|
|
76
|
+
.partial(),
|
|
77
|
+
auth: z
|
|
78
|
+
.object({
|
|
79
|
+
origin: z.string(),
|
|
80
|
+
/** In minutes */
|
|
81
|
+
passkey_probation: z.number(),
|
|
82
|
+
rp_id: z.string(),
|
|
83
|
+
rp_name: z.string(),
|
|
84
|
+
secure_cookies: z.boolean(),
|
|
85
|
+
/** In minutes */
|
|
86
|
+
verification_timeout: z.number(),
|
|
87
|
+
/** Whether users can verify emails */
|
|
88
|
+
email_verification: z.boolean(),
|
|
89
|
+
})
|
|
90
|
+
.partial(),
|
|
91
|
+
db: z
|
|
92
|
+
.object({
|
|
93
|
+
host: z.string(),
|
|
94
|
+
port: z.number(),
|
|
95
|
+
password: z.string(),
|
|
96
|
+
user: z.string(),
|
|
97
|
+
database: z.string(),
|
|
98
|
+
})
|
|
99
|
+
.partial(),
|
|
100
|
+
debug: z.boolean(),
|
|
101
|
+
log: z
|
|
102
|
+
.object({
|
|
103
|
+
level: z.enum(levelText),
|
|
104
|
+
console: z.boolean(),
|
|
105
|
+
})
|
|
106
|
+
.partial(),
|
|
107
|
+
web: z
|
|
108
|
+
.object({
|
|
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(),
|
|
115
|
+
})
|
|
116
|
+
.partial(),
|
|
100
117
|
include: z.array(z.string()).optional(),
|
|
101
118
|
plugins: z.array(z.string()).optional(),
|
|
102
119
|
})
|
|
103
|
-
.
|
|
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
|
+
}
|
|
104
132
|
/**
|
|
105
133
|
* Update the current config
|
|
106
134
|
*/
|
|
@@ -109,11 +137,14 @@ export function setConfig(other) {
|
|
|
109
137
|
logger.detach(output);
|
|
110
138
|
if (config.log.console)
|
|
111
139
|
logger.attach(output, { output: config.log.level });
|
|
140
|
+
_setDebugOutput(config.debug);
|
|
112
141
|
}
|
|
113
142
|
/**
|
|
114
143
|
* Load the config from the provided path
|
|
115
144
|
*/
|
|
116
145
|
export async function loadConfig(path, options = {}) {
|
|
146
|
+
if (configFiles.has(path))
|
|
147
|
+
return;
|
|
117
148
|
let json;
|
|
118
149
|
try {
|
|
119
150
|
json = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -127,13 +158,14 @@ export async function loadConfig(path, options = {}) {
|
|
|
127
158
|
const file = options.strict ? File.parse(json) : json;
|
|
128
159
|
configFiles.set(path, file);
|
|
129
160
|
setConfig(file);
|
|
161
|
+
output.debug('Loaded config: ' + path);
|
|
130
162
|
for (const include of file.include ?? [])
|
|
131
163
|
await loadConfig(join(dirname(path), include), { optional: true });
|
|
132
164
|
for (const plugin of file.plugins ?? [])
|
|
133
165
|
await loadPlugin(plugin.startsWith('.') ? resolve(dirname(path), plugin) : plugin);
|
|
134
166
|
}
|
|
135
167
|
export async function loadDefaultConfigs() {
|
|
136
|
-
for (const path of
|
|
168
|
+
for (const path of findConfigPaths()) {
|
|
137
169
|
if (!existsSync(path))
|
|
138
170
|
writeFileSync(path, '{}');
|
|
139
171
|
await loadConfig(path, { optional: true });
|
|
@@ -143,7 +175,7 @@ export async function loadDefaultConfigs() {
|
|
|
143
175
|
* Update the current config and write the updated config to the appropriate file
|
|
144
176
|
*/
|
|
145
177
|
export function saveConfig(changed, global = false) {
|
|
146
|
-
saveConfigTo(
|
|
178
|
+
saveConfigTo(findConfigPaths().at(global ? 0 : -1), changed);
|
|
147
179
|
}
|
|
148
180
|
/**
|
|
149
181
|
* Update the current config and write the updated config to the provided path
|
|
@@ -158,10 +190,11 @@ export function saveConfigTo(path, changed) {
|
|
|
158
190
|
/**
|
|
159
191
|
* Find the path to the config file
|
|
160
192
|
*/
|
|
161
|
-
export function
|
|
193
|
+
export function findConfigPaths() {
|
|
194
|
+
const paths = dirs.map(dir => join(dir, 'config.json'));
|
|
162
195
|
if (process.env.AXIUM_CONFIG)
|
|
163
|
-
|
|
164
|
-
return
|
|
196
|
+
paths.push(process.env.AXIUM_CONFIG);
|
|
197
|
+
return paths;
|
|
165
198
|
}
|
|
166
199
|
if (process.env.AXIUM_CONFIG)
|
|
167
200
|
await loadConfig(process.env.AXIUM_CONFIG);
|
package/dist/database.d.ts
CHANGED
|
@@ -38,8 +38,7 @@ export interface Schema {
|
|
|
38
38
|
transports: AuthenticatorTransportFuture[];
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
export
|
|
42
|
-
}
|
|
41
|
+
export type Database = Kysely<Schema> & AsyncDisposable;
|
|
43
42
|
export declare let database: Database;
|
|
44
43
|
export declare function connect(): Database;
|
|
45
44
|
export interface Stats {
|
|
@@ -47,7 +46,7 @@ export interface Stats {
|
|
|
47
46
|
passkeys: number;
|
|
48
47
|
sessions: number;
|
|
49
48
|
}
|
|
50
|
-
export declare function count(table:
|
|
49
|
+
export declare function count<const T extends keyof Schema>(table: T): Promise<number>;
|
|
51
50
|
export declare function status(): Promise<Stats>;
|
|
52
51
|
export declare function statusText(): Promise<string>;
|
|
53
52
|
export interface OpOptions extends MaybeOutput {
|
|
@@ -65,6 +64,7 @@ export interface PluginShortcuts {
|
|
|
65
64
|
}
|
|
66
65
|
export declare function init(opt: InitOptions): Promise<void>;
|
|
67
66
|
export declare function check(opt: OpOptions): Promise<void>;
|
|
67
|
+
export declare function clean(opt: Partial<OpOptions>): Promise<void>;
|
|
68
68
|
/**
|
|
69
69
|
* Completely remove Axium from the database.
|
|
70
70
|
*/
|
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,11 +72,14 @@ 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) {
|
|
75
79
|
const db = connect();
|
|
76
|
-
return (await db.selectFrom(table)
|
|
80
|
+
return (await db.selectFrom(table)
|
|
81
|
+
.select(db.fn.countAll().as('count'))
|
|
82
|
+
.executeTakeFirstOrThrow()).count;
|
|
77
83
|
}
|
|
78
84
|
export async function status() {
|
|
79
85
|
return {
|
|
@@ -286,35 +292,62 @@ export async function check(opt) {
|
|
|
286
292
|
await result_2;
|
|
287
293
|
}
|
|
288
294
|
}
|
|
289
|
-
|
|
290
|
-
* Completely remove Axium from the database.
|
|
291
|
-
*/
|
|
292
|
-
export async function uninstall(opt) {
|
|
295
|
+
export async function clean(opt) {
|
|
293
296
|
_fixOutput(opt);
|
|
297
|
+
const done = () => opt.output('done');
|
|
298
|
+
const now = new Date();
|
|
294
299
|
const db = connect();
|
|
300
|
+
opt.output('start', 'Removing expired sessions');
|
|
301
|
+
await db.deleteFrom('sessions').where('sessions.expires', '<', now).execute().then(done);
|
|
302
|
+
opt.output('start', 'Removing expired verifications');
|
|
303
|
+
await db.deleteFrom('verifications').where('verifications.expires', '<', now).execute().then(done);
|
|
295
304
|
for (const plugin of plugins) {
|
|
296
|
-
if (!plugin.
|
|
305
|
+
if (!plugin.db_clean)
|
|
297
306
|
continue;
|
|
298
307
|
opt.output('plugin', plugin.name);
|
|
299
|
-
await plugin.
|
|
308
|
+
await plugin.db_clean(opt, db);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Completely remove Axium from the database.
|
|
313
|
+
*/
|
|
314
|
+
export async function uninstall(opt) {
|
|
315
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
316
|
+
try {
|
|
317
|
+
_fixOutput(opt);
|
|
318
|
+
const db = __addDisposableResource(env_3, connect(), true);
|
|
319
|
+
for (const plugin of plugins) {
|
|
320
|
+
if (!plugin.db_remove)
|
|
321
|
+
continue;
|
|
322
|
+
opt.output('plugin', plugin.name);
|
|
323
|
+
await plugin.db_remove(opt, db);
|
|
324
|
+
}
|
|
325
|
+
const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
|
|
326
|
+
await _sql('DROP DATABASE axium', 'Dropping database');
|
|
327
|
+
await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
|
|
328
|
+
await _sql('DROP USER axium', 'Dropping user');
|
|
329
|
+
await getHBA(opt)
|
|
330
|
+
.then(([content, writeBack]) => {
|
|
331
|
+
opt.output('start', 'Checking for Axium HBA configuration');
|
|
332
|
+
if (!content.includes(pgHba))
|
|
333
|
+
throw 'missing.';
|
|
334
|
+
opt.output('done');
|
|
335
|
+
opt.output('start', 'Removing Axium HBA configuration');
|
|
336
|
+
const newContent = content.replace(pgHba, '');
|
|
337
|
+
opt.output('done');
|
|
338
|
+
writeBack(newContent);
|
|
339
|
+
})
|
|
340
|
+
.catch(e => opt.output('warn', e));
|
|
341
|
+
}
|
|
342
|
+
catch (e_3) {
|
|
343
|
+
env_3.error = e_3;
|
|
344
|
+
env_3.hasError = true;
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
const result_3 = __disposeResources(env_3);
|
|
348
|
+
if (result_3)
|
|
349
|
+
await result_3;
|
|
300
350
|
}
|
|
301
|
-
await db.destroy();
|
|
302
|
-
const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
|
|
303
|
-
await _sql('DROP DATABASE axium', 'Dropping database');
|
|
304
|
-
await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
|
|
305
|
-
await _sql('DROP USER axium', 'Dropping user');
|
|
306
|
-
await getHBA(opt)
|
|
307
|
-
.then(([content, writeBack]) => {
|
|
308
|
-
opt.output('start', 'Checking for Axium HBA configuration');
|
|
309
|
-
if (!content.includes(pgHba))
|
|
310
|
-
throw 'missing.';
|
|
311
|
-
opt.output('done');
|
|
312
|
-
opt.output('start', 'Removing Axium HBA configuration');
|
|
313
|
-
const newContent = content.replace(pgHba, '');
|
|
314
|
-
opt.output('done');
|
|
315
|
-
writeBack(newContent);
|
|
316
|
-
})
|
|
317
|
-
.catch(e => opt.output('warn', e));
|
|
318
351
|
}
|
|
319
352
|
/**
|
|
320
353
|
* Removes all data from tables.
|
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>;
|
|
@@ -9,12 +8,16 @@ export declare const Plugin: z.ZodObject<{
|
|
|
9
8
|
db_init: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
|
|
10
9
|
db_remove: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
|
|
11
10
|
db_wipe: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
|
|
11
|
+
db_clean: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
|
|
12
12
|
}, z.core.$strip>;
|
|
13
|
+
declare const kSpecifier: unique symbol;
|
|
13
14
|
export interface Plugin extends z.infer<typeof Plugin> {
|
|
15
|
+
[kSpecifier]: string;
|
|
14
16
|
}
|
|
15
17
|
export declare const plugins: Set<Plugin>;
|
|
16
18
|
export declare function resolvePlugin(search: string): Plugin | undefined;
|
|
17
19
|
export declare function pluginText(plugin: Plugin): string;
|
|
18
20
|
export declare function loadPlugin(specifier: string): Promise<void>;
|
|
21
|
+
export declare function getSpecifier(plugin: Plugin): string;
|
|
19
22
|
export declare function loadPlugins(dir: string): Promise<void>;
|
|
20
|
-
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(),
|
|
@@ -14,18 +14,19 @@ export const Plugin = z.object({
|
|
|
14
14
|
db_init: fn.optional(),
|
|
15
15
|
db_remove: fn.optional(),
|
|
16
16
|
db_wipe: fn.optional(),
|
|
17
|
+
db_clean: fn.optional(),
|
|
17
18
|
});
|
|
18
|
-
|
|
19
|
+
const kSpecifier = Symbol('specifier');
|
|
20
|
+
export const plugins = _unique('plugins', new Set());
|
|
19
21
|
export function resolvePlugin(search) {
|
|
20
22
|
for (const plugin of plugins) {
|
|
21
|
-
if (plugin.name.startsWith(search)
|
|
23
|
+
if (plugin.name.startsWith(search))
|
|
22
24
|
return plugin;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
export function pluginText(plugin) {
|
|
26
28
|
return [
|
|
27
29
|
styleText('whiteBright', plugin.name),
|
|
28
|
-
plugin.id,
|
|
29
30
|
`Version: ${plugin.version}`,
|
|
30
31
|
`Description: ${plugin.description ?? styleText('dim', '(none)')}`,
|
|
31
32
|
`Database integration: ${[plugin.db_init, plugin.db_remove, plugin.db_wipe]
|
|
@@ -36,16 +37,21 @@ export function pluginText(plugin) {
|
|
|
36
37
|
}
|
|
37
38
|
export async function loadPlugin(specifier) {
|
|
38
39
|
try {
|
|
39
|
-
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 => {
|
|
40
43
|
throw z.prettifyError(e);
|
|
41
|
-
});
|
|
44
|
+
}), { [kSpecifier]: specifier });
|
|
42
45
|
plugins.add(plugin);
|
|
43
|
-
output.debug(`Loaded plugin:
|
|
46
|
+
output.debug(`Loaded plugin: ${plugin.name} ${plugin.version}`);
|
|
44
47
|
}
|
|
45
48
|
catch (e) {
|
|
46
49
|
output.debug(`Failed to load plugin from ${specifier}: ${e.message || e}`);
|
|
47
50
|
}
|
|
48
51
|
}
|
|
52
|
+
export function getSpecifier(plugin) {
|
|
53
|
+
return plugin[kSpecifier];
|
|
54
|
+
}
|
|
49
55
|
export async function loadPlugins(dir) {
|
|
50
56
|
fs.mkdirSync(dir, { recursive: true });
|
|
51
57
|
const files = fs.readdirSync(dir);
|
|
@@ -57,7 +63,3 @@ export async function loadPlugins(dir) {
|
|
|
57
63
|
await loadPlugin(path);
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
|
-
export async function loadDefaultPlugins() {
|
|
61
|
-
await loadPlugins(join(findDir(true), 'plugins'));
|
|
62
|
-
await loadPlugins(join(findDir(false), 'plugins'));
|
|
63
|
-
}
|
|
@@ -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
|
+
}
|