@axium/server 0.7.6 → 0.9.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/dist/apps.d.ts +15 -0
- package/dist/apps.js +20 -0
- package/dist/auth.d.ts +53 -30
- package/dist/auth.js +103 -130
- package/dist/cli.js +176 -42
- package/dist/config.d.ts +57 -312
- package/dist/config.js +65 -31
- package/dist/database.d.ts +31 -40
- package/dist/database.js +165 -62
- package/dist/io.js +6 -2
- package/dist/plugins.d.ts +8 -24
- package/dist/plugins.js +10 -14
- package/dist/routes.d.ts +55 -0
- package/dist/routes.js +54 -0
- package/package.json +11 -16
- package/web/api/index.ts +7 -0
- package/web/api/metadata.ts +35 -0
- package/web/api/passkeys.ts +56 -0
- package/web/api/readme.md +1 -0
- package/web/api/register.ts +83 -0
- package/web/api/schemas.ts +22 -0
- package/web/api/session.ts +33 -0
- package/web/api/users.ts +351 -0
- package/web/api/utils.ts +66 -0
- package/web/auth.ts +1 -5
- package/web/hooks.server.ts +12 -1
- package/web/index.server.ts +0 -1
- package/web/lib/ClipboardCopy.svelte +42 -0
- package/web/lib/Dialog.svelte +3 -6
- package/web/lib/FormDialog.svelte +61 -14
- package/web/lib/Toast.svelte +8 -1
- package/web/lib/UserCard.svelte +1 -1
- package/web/lib/auth.ts +12 -0
- package/web/lib/icons/Icon.svelte +7 -13
- package/web/lib/index.ts +0 -2
- package/web/lib/styles.css +18 -1
- package/web/routes/+layout.svelte +1 -1
- package/web/routes/[...path]/+page.server.ts +13 -0
- package/web/routes/[appId]/[...page]/+page.server.ts +14 -0
- package/web/routes/_axium/default/+page.svelte +11 -0
- package/web/routes/account/+page.svelte +291 -0
- package/web/routes/api/[...path]/+server.ts +49 -0
- package/web/routes/login/+page.svelte +25 -0
- package/web/routes/logout/+page.svelte +13 -0
- package/web/routes/register/+page.svelte +21 -0
- package/web/tsconfig.json +2 -1
- package/web/utils.ts +9 -15
- package/web/actions.ts +0 -58
- package/web/lib/Account.svelte +0 -76
- package/web/lib/SignUp.svelte +0 -20
- package/web/lib/account.css +0 -36
- package/web/routes/+page.server.ts +0 -16
- package/web/routes/+page.svelte +0 -10
- package/web/routes/name/+page.server.ts +0 -5
- package/web/routes/name/+page.svelte +0 -20
- package/web/routes/signup/+page.server.ts +0 -10
- package/web/routes/signup/+page.svelte +0 -15
package/dist/apps.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LoadEvent, RequestEvent } from '@sveltejs/kit';
|
|
2
|
+
import type { WebRoute, WebRouteOptions } from './routes.js';
|
|
3
|
+
export interface CreateAppOptions {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const apps: Map<string, App>;
|
|
8
|
+
export declare class App {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
protected readonly routes: Map<string, WebRoute>;
|
|
12
|
+
constructor(opt: CreateAppOptions);
|
|
13
|
+
addRoute(route: WebRouteOptions): void;
|
|
14
|
+
resolveRoute(event: RequestEvent | LoadEvent): WebRoute | undefined;
|
|
15
|
+
}
|
package/dist/apps.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pick } from 'utilium';
|
|
2
|
+
import { addRoute, resolveRoute } from './routes.js';
|
|
3
|
+
export const apps = new Map();
|
|
4
|
+
export class App {
|
|
5
|
+
id;
|
|
6
|
+
name;
|
|
7
|
+
routes = new Map();
|
|
8
|
+
constructor(opt) {
|
|
9
|
+
if (apps.has(opt.id))
|
|
10
|
+
throw new ReferenceError(`App with ID "${opt.id}" already exists.`);
|
|
11
|
+
Object.assign(this, pick(opt, 'id', 'name'));
|
|
12
|
+
apps.set(this.id, this);
|
|
13
|
+
}
|
|
14
|
+
addRoute(route) {
|
|
15
|
+
addRoute(route, this.routes);
|
|
16
|
+
}
|
|
17
|
+
resolveRoute(event) {
|
|
18
|
+
return resolveRoute(event, this.routes);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,35 +1,58 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* User preferences.
|
|
7
|
-
* Modify with `declare module ...`
|
|
8
|
-
*/
|
|
9
|
-
export interface Preferences {
|
|
1
|
+
import type { Passkey, Session, Verification } from '@axium/core/api';
|
|
2
|
+
import type { User } from '@axium/core/user';
|
|
3
|
+
export interface UserInternal extends User {
|
|
4
|
+
password?: string | null;
|
|
5
|
+
salt?: string | null;
|
|
10
6
|
}
|
|
11
|
-
declare
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
export declare function getUser(id: string): Promise<{
|
|
8
|
+
name: string;
|
|
9
|
+
id: string;
|
|
10
|
+
image: string | null | undefined;
|
|
11
|
+
email: string;
|
|
12
|
+
emailVerified: Date | null | undefined;
|
|
13
|
+
preferences: import("@axium/core/user").Preferences | undefined;
|
|
14
|
+
} | null>;
|
|
15
|
+
export declare function getUserByEmail(email: string): Promise<{
|
|
16
|
+
name: string;
|
|
17
|
+
id: string;
|
|
18
|
+
image: string | null | undefined;
|
|
19
|
+
email: string;
|
|
20
|
+
emailVerified: Date | null | undefined;
|
|
21
|
+
preferences: import("@axium/core/user").Preferences | undefined;
|
|
22
|
+
} | null>;
|
|
23
|
+
export declare function updateUser({ id, ...user }: UserInternal): Promise<{
|
|
24
|
+
name: string;
|
|
25
|
+
id: string;
|
|
26
|
+
image: string | null | undefined;
|
|
27
|
+
email: string;
|
|
28
|
+
emailVerified: Date | null | undefined;
|
|
29
|
+
preferences: import("@axium/core/user").Preferences | undefined;
|
|
30
|
+
}>;
|
|
31
|
+
export interface SessionInternal extends Session {
|
|
32
|
+
token: string;
|
|
17
33
|
}
|
|
18
|
-
export declare
|
|
19
|
-
export declare function
|
|
20
|
-
|
|
21
|
-
* Login using credentials
|
|
22
|
-
*/
|
|
23
|
-
export declare function register(credentials: Registration): Promise<{
|
|
24
|
-
user: import("@auth/core/adapters").AdapterUser;
|
|
25
|
-
session: import("@auth/core/adapters").AdapterSession;
|
|
34
|
+
export declare function createSession(userId: string, elevated?: boolean): Promise<SessionInternal>;
|
|
35
|
+
export declare function getSessionAndUser(token: string): Promise<SessionInternal & {
|
|
36
|
+
user: UserInternal | null;
|
|
26
37
|
}>;
|
|
38
|
+
export declare function getSession(sessionId: string): Promise<SessionInternal>;
|
|
39
|
+
export declare function getSessions(userId: string): Promise<SessionInternal[]>;
|
|
40
|
+
export type VerificationRole = 'verify_email' | 'login';
|
|
41
|
+
export interface VerificationInternal extends Verification {
|
|
42
|
+
token: string;
|
|
43
|
+
role: VerificationRole;
|
|
44
|
+
}
|
|
27
45
|
/**
|
|
28
|
-
*
|
|
46
|
+
* Create a verification
|
|
47
|
+
* @param expires How long the token should be valid for in seconds
|
|
29
48
|
*/
|
|
30
|
-
export declare function
|
|
31
|
-
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
export declare function createVerification(role: VerificationRole, userId: string, expires: number): Promise<VerificationInternal>;
|
|
50
|
+
export declare function useVerification(role: VerificationRole, userId: string, token: string): Promise<VerificationInternal | undefined>;
|
|
51
|
+
export interface PasskeyInternal extends Passkey {
|
|
52
|
+
publicKey: Uint8Array;
|
|
53
|
+
counter: number;
|
|
54
|
+
}
|
|
55
|
+
export declare function getPasskey(id: string): Promise<PasskeyInternal | null>;
|
|
56
|
+
export declare function createPasskey(passkey: Omit<PasskeyInternal, 'createdAt'>): Promise<PasskeyInternal>;
|
|
57
|
+
export declare function getPasskeysByUserId(userId: string): Promise<PasskeyInternal[]>;
|
|
58
|
+
export declare function updatePasskeyCounter(id: PasskeyInternal['id'], newCounter: PasskeyInternal['counter']): Promise<PasskeyInternal>;
|
package/dist/auth.js
CHANGED
|
@@ -1,137 +1,110 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import * as db from './database.js';
|
|
11
|
-
import { logger } from './io.js';
|
|
12
|
-
export let adapter;
|
|
13
|
-
export function createAdapter() {
|
|
14
|
-
if (adapter)
|
|
15
|
-
return;
|
|
16
|
-
const conn = db.connect();
|
|
17
|
-
adapter = Object.assign(KyselyAdapter(conn), {
|
|
18
|
-
async getAccount(providerAccountId, provider) {
|
|
19
|
-
const result = await conn.selectFrom('Account').selectAll().where('providerAccountId', '=', providerAccountId).where('provider', '=', provider).executeTakeFirst();
|
|
20
|
-
return result ?? null;
|
|
21
|
-
},
|
|
22
|
-
async getAuthenticator(credentialID) {
|
|
23
|
-
const result = await conn.selectFrom('Authenticator').selectAll().where('credentialID', '=', credentialID).executeTakeFirst();
|
|
24
|
-
return result ?? null;
|
|
25
|
-
},
|
|
26
|
-
async createAuthenticator(authenticator) {
|
|
27
|
-
await conn.insertInto('Authenticator').values(authenticator).executeTakeFirstOrThrow();
|
|
28
|
-
return authenticator;
|
|
29
|
-
},
|
|
30
|
-
async listAuthenticatorsByUserId(userId) {
|
|
31
|
-
const result = await conn.selectFrom('Authenticator').selectAll().where('userId', '=', userId).execute();
|
|
32
|
-
return result;
|
|
33
|
-
},
|
|
34
|
-
async updateAuthenticatorCounter(credentialID, newCounter) {
|
|
35
|
-
await conn.updateTable('Authenticator').set({ counter: newCounter }).where('credentialID', '=', credentialID).executeTakeFirstOrThrow();
|
|
36
|
-
const authenticator = await adapter.getAuthenticator?.(credentialID);
|
|
37
|
-
if (!authenticator)
|
|
38
|
-
throw new Error('Authenticator not found');
|
|
39
|
-
return authenticator;
|
|
40
|
-
},
|
|
41
|
-
});
|
|
1
|
+
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
|
2
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
3
|
+
import { connect, database as db } from './database.js';
|
|
4
|
+
export async function getUser(id) {
|
|
5
|
+
connect();
|
|
6
|
+
const result = await db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst();
|
|
7
|
+
if (!result)
|
|
8
|
+
return null;
|
|
9
|
+
return result;
|
|
42
10
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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;
|
|
17
|
+
}
|
|
18
|
+
export async function updateUser({ id, ...user }) {
|
|
19
|
+
connect();
|
|
20
|
+
const query = db.updateTable('users').set(user).where('id', '=', id);
|
|
21
|
+
return await query.returningAll().executeTakeFirstOrThrow();
|
|
22
|
+
}
|
|
23
|
+
const in30days = () => new Date(Date.now() + 2592000000);
|
|
24
|
+
const in10minutes = () => new Date(Date.now() + 600000);
|
|
25
|
+
export async function createSession(userId, elevated = false) {
|
|
26
|
+
connect();
|
|
27
|
+
const session = {
|
|
28
|
+
id: randomUUID(),
|
|
29
|
+
userId,
|
|
30
|
+
token: randomBytes(64).toString('base64'),
|
|
31
|
+
expires: elevated ? in10minutes() : in30days(),
|
|
32
|
+
elevated,
|
|
33
|
+
created: new Date(),
|
|
34
|
+
};
|
|
35
|
+
await db.insertInto('sessions').values(session).execute();
|
|
36
|
+
return session;
|
|
37
|
+
}
|
|
38
|
+
export async function getSessionAndUser(token) {
|
|
39
|
+
connect();
|
|
40
|
+
const result = await db
|
|
41
|
+
.selectFrom('sessions')
|
|
42
|
+
.selectAll()
|
|
43
|
+
.select(eb => jsonObjectFrom(eb.selectFrom('users').selectAll().whereRef('users.id', '=', 'sessions.userId')).as('user'))
|
|
44
|
+
.where('sessions.token', '=', token)
|
|
45
|
+
.where('sessions.expires', '>', new Date())
|
|
46
|
+
.executeTakeFirst();
|
|
47
|
+
if (!result)
|
|
48
|
+
throw new Error('Session not found');
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
export async function getSession(sessionId) {
|
|
52
|
+
connect();
|
|
53
|
+
return await db
|
|
54
|
+
.selectFrom('sessions')
|
|
55
|
+
.selectAll()
|
|
56
|
+
.where('id', '=', sessionId)
|
|
57
|
+
.where('sessions.expires', '>', new Date())
|
|
58
|
+
.executeTakeFirstOrThrow();
|
|
59
|
+
}
|
|
60
|
+
export async function getSessions(userId) {
|
|
61
|
+
connect();
|
|
62
|
+
return await db.selectFrom('sessions').selectAll().where('userId', '=', userId).where('sessions.expires', '>', new Date()).execute();
|
|
72
63
|
}
|
|
73
64
|
/**
|
|
74
|
-
*
|
|
65
|
+
* Create a verification
|
|
66
|
+
* @param expires How long the token should be valid for in seconds
|
|
75
67
|
*/
|
|
76
|
-
export async function
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
export async function createVerification(role, userId, expires) {
|
|
69
|
+
const token = randomBytes(64).toString('base64url');
|
|
70
|
+
const verification = { userId, token, expires: new Date(Date.now() + expires * 1000), role };
|
|
71
|
+
connect();
|
|
72
|
+
await db.insertInto('verifications').values(verification).executeTakeFirstOrThrow();
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
void db.deleteFrom('verifications').where('verifications.token', '=', verification.token).execute();
|
|
75
|
+
}, expires * 1000);
|
|
76
|
+
return verification;
|
|
77
|
+
}
|
|
78
|
+
export async function useVerification(role, userId, token) {
|
|
79
|
+
connect();
|
|
80
|
+
const query = db
|
|
81
|
+
.deleteFrom('verifications')
|
|
82
|
+
.where('verifications.token', '=', token)
|
|
83
|
+
.where('verifications.userId', '=', userId)
|
|
84
|
+
.where('verifications.role', '=', role);
|
|
85
|
+
return await query.returningAll().executeTakeFirst();
|
|
86
|
+
}
|
|
87
|
+
export async function getPasskey(id) {
|
|
88
|
+
connect();
|
|
89
|
+
const result = await db.selectFrom('passkeys').selectAll().where('id', '=', id).executeTakeFirst();
|
|
90
|
+
if (!result)
|
|
84
91
|
return null;
|
|
85
|
-
return
|
|
92
|
+
return result;
|
|
86
93
|
}
|
|
87
|
-
export function
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
debug,
|
|
104
|
-
experimental: { enableWebAuthn: true },
|
|
105
|
-
secret: config.auth.secret,
|
|
106
|
-
useSecureCookies: config.auth.secure_cookies,
|
|
107
|
-
session: { strategy: 'database' },
|
|
108
|
-
logger: {
|
|
109
|
-
error(error) {
|
|
110
|
-
logger.error('[auth] ' + error.message);
|
|
111
|
-
},
|
|
112
|
-
warn(code) {
|
|
113
|
-
switch (code) {
|
|
114
|
-
case 'experimental-webauthn':
|
|
115
|
-
case 'debug-enabled':
|
|
116
|
-
return;
|
|
117
|
-
case 'csrf-disabled':
|
|
118
|
-
logger.warn('CSRF protection is disabled.');
|
|
119
|
-
break;
|
|
120
|
-
case 'env-url-basepath-redundant':
|
|
121
|
-
case 'env-url-basepath-mismatch':
|
|
122
|
-
default:
|
|
123
|
-
logger.warn('[auth] ' + code);
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
debug(message, metadata) {
|
|
127
|
-
debug && logger.debug('[auth]', message, metadata ? JSON.stringify(metadata, (k, v) => (k && JSON.stringify(v).length > 100 ? '...' : v)) : '');
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
callbacks: {
|
|
131
|
-
signIn({ user }) {
|
|
132
|
-
logger.info('[auth] signin', user.id ?? '', user.email ? `(${user.email})` : '');
|
|
133
|
-
return true;
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
};
|
|
94
|
+
export async function createPasskey(passkey) {
|
|
95
|
+
connect();
|
|
96
|
+
const result = await db.insertInto('passkeys').values(passkey).returningAll().executeTakeFirstOrThrow();
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
export async function getPasskeysByUserId(userId) {
|
|
100
|
+
connect();
|
|
101
|
+
return await db.selectFrom('passkeys').selectAll().where('userId', '=', userId).execute();
|
|
102
|
+
}
|
|
103
|
+
export async function updatePasskeyCounter(id, newCounter) {
|
|
104
|
+
connect();
|
|
105
|
+
await db.updateTable('passkeys').set({ counter: newCounter }).where('id', '=', id).executeTakeFirstOrThrow();
|
|
106
|
+
const passkey = await getPasskey(id);
|
|
107
|
+
if (!passkey)
|
|
108
|
+
throw new Error('Passkey not found');
|
|
109
|
+
return passkey;
|
|
137
110
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
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';
|
|
3
|
-
import { randomBytes } from 'node:crypto';
|
|
4
55
|
import { styleText } from 'node:util';
|
|
5
56
|
import { getByString, isJSON, setByString } from 'utilium';
|
|
6
57
|
import $pkg from '../package.json' with { type: 'json' };
|
|
@@ -8,6 +59,7 @@ import config from './config.js';
|
|
|
8
59
|
import * as db from './database.js';
|
|
9
60
|
import { _portActions, _portMethods, exit, handleError, output, restrictedPorts } from './io.js';
|
|
10
61
|
import { loadDefaultPlugins, plugins, pluginText, resolvePlugin } from './plugins.js';
|
|
62
|
+
import { apps } from './apps.js';
|
|
11
63
|
program
|
|
12
64
|
.version($pkg.version)
|
|
13
65
|
.name('axium')
|
|
@@ -25,10 +77,10 @@ program.hook('preAction', async function (_, action) {
|
|
|
25
77
|
opt.force && output.warn('--force: Protections disabled.');
|
|
26
78
|
if (opt.debug === false)
|
|
27
79
|
config.set({ debug: false });
|
|
28
|
-
if (!config.auth.secret) {
|
|
80
|
+
/* if (!config.auth.secret) {
|
|
29
81
|
config.save({ auth: { secret: process.env.AUTH_SECRET || randomBytes(32).toString('base64') } }, true);
|
|
30
82
|
output.debug('Auto-generated a new auth secret');
|
|
31
|
-
}
|
|
83
|
+
} */
|
|
32
84
|
});
|
|
33
85
|
// Options shared by multiple (sub)commands
|
|
34
86
|
const opts = {
|
|
@@ -76,32 +128,82 @@ axiumDB
|
|
|
76
128
|
.description('Drop the Axium database and user')
|
|
77
129
|
.addOption(opts.force)
|
|
78
130
|
.action(async (opt) => {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
132
|
+
try {
|
|
133
|
+
const _ = __addDisposableResource(env_1, db.connect(), true);
|
|
134
|
+
const stats = await db.status().catch(exit);
|
|
135
|
+
if (!opt.force)
|
|
136
|
+
for (const key of ['users', 'passkeys', 'sessions']) {
|
|
137
|
+
if (stats[key] == 0)
|
|
138
|
+
continue;
|
|
139
|
+
output.warn(`Database has existing ${key}. Use --force if you really want to drop the database.`);
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
await db.uninstall(opt).catch(exit);
|
|
143
|
+
}
|
|
144
|
+
catch (e_1) {
|
|
145
|
+
env_1.error = e_1;
|
|
146
|
+
env_1.hasError = true;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
const result_1 = __disposeResources(env_1);
|
|
150
|
+
if (result_1)
|
|
151
|
+
await result_1;
|
|
152
|
+
}
|
|
89
153
|
});
|
|
90
154
|
axiumDB
|
|
91
155
|
.command('wipe')
|
|
92
156
|
.description('Wipe the database')
|
|
93
157
|
.addOption(opts.force)
|
|
94
158
|
.action(async (opt) => {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
160
|
+
try {
|
|
161
|
+
const _ = __addDisposableResource(env_2, db.connect(), true);
|
|
162
|
+
const stats = await db.status().catch(exit);
|
|
163
|
+
if (!opt.force)
|
|
164
|
+
for (const key of ['users', 'passkeys', 'sessions']) {
|
|
165
|
+
if (stats[key] == 0)
|
|
166
|
+
continue;
|
|
167
|
+
output.warn(`Database has existing ${key}. Use --force if you really want to wipe the database.`);
|
|
168
|
+
process.exit(2);
|
|
169
|
+
}
|
|
170
|
+
await db.wipe(opt).catch(exit);
|
|
171
|
+
}
|
|
172
|
+
catch (e_2) {
|
|
173
|
+
env_2.error = e_2;
|
|
174
|
+
env_2.hasError = true;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
const result_2 = __disposeResources(env_2);
|
|
178
|
+
if (result_2)
|
|
179
|
+
await result_2;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
axiumDB
|
|
183
|
+
.command('check')
|
|
184
|
+
.description('Check the structure of the database')
|
|
185
|
+
.action(async (opt) => {
|
|
186
|
+
await db.check(opt).catch(exit);
|
|
187
|
+
});
|
|
188
|
+
axiumDB
|
|
189
|
+
.command('clean')
|
|
190
|
+
.description('Remove expired rows')
|
|
191
|
+
.addOption(opts.force)
|
|
192
|
+
.action(async (opt) => {
|
|
193
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
194
|
+
try {
|
|
195
|
+
const _ = __addDisposableResource(env_3, db.connect(), true);
|
|
196
|
+
await db.clean(opt).catch(exit);
|
|
197
|
+
}
|
|
198
|
+
catch (e_3) {
|
|
199
|
+
env_3.error = e_3;
|
|
200
|
+
env_3.hasError = true;
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
const result_3 = __disposeResources(env_3);
|
|
204
|
+
if (result_3)
|
|
205
|
+
await result_3;
|
|
206
|
+
}
|
|
105
207
|
});
|
|
106
208
|
const axiumConfig = program
|
|
107
209
|
.command('config')
|
|
@@ -111,7 +213,6 @@ const axiumConfig = program
|
|
|
111
213
|
.option('-r, --redact', 'Do not output sensitive values');
|
|
112
214
|
function configReplacer(opt) {
|
|
113
215
|
return (key, value) => {
|
|
114
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
115
216
|
return opt.redact && ['password', 'secret'].includes(key) ? '[redacted]' : value;
|
|
116
217
|
};
|
|
117
218
|
}
|
|
@@ -191,33 +292,66 @@ axiumPlugin
|
|
|
191
292
|
exit(`Can't find a plugin matching "${search}"`);
|
|
192
293
|
console.log(pluginText(plugin));
|
|
193
294
|
});
|
|
295
|
+
const axiumApps = program.command('apps').description('Manage Axium apps').addOption(opts.global);
|
|
296
|
+
axiumApps
|
|
297
|
+
.command('list')
|
|
298
|
+
.alias('ls')
|
|
299
|
+
.description('List apps added by plugins')
|
|
300
|
+
.option('-l, --long', 'use the long listing format')
|
|
301
|
+
.option('-b, --builtin', 'include built-in apps')
|
|
302
|
+
.action((opt) => {
|
|
303
|
+
if (!apps.size) {
|
|
304
|
+
console.log('No apps.');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (!opt.long) {
|
|
308
|
+
console.log(Array.from(apps.values().map(app => app.name)).join(', '));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
console.log(styleText('whiteBright', apps.size + ' app(s) loaded:'));
|
|
312
|
+
for (const app of apps.values()) {
|
|
313
|
+
console.log(app.name, styleText('dim', `(${app.id})`));
|
|
314
|
+
}
|
|
315
|
+
});
|
|
194
316
|
program
|
|
195
317
|
.command('status')
|
|
196
318
|
.alias('stats')
|
|
197
319
|
.description('Get information about the server')
|
|
198
320
|
.addOption(opts.host)
|
|
199
321
|
.action(async () => {
|
|
200
|
-
|
|
201
|
-
console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
202
|
-
console.log(styleText('whiteBright', 'Loaded config files:'), config.files.keys().toArray().join(', '));
|
|
203
|
-
process.stdout.write(styleText('whiteBright', 'Database: '));
|
|
322
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
204
323
|
try {
|
|
205
|
-
console.log(
|
|
324
|
+
console.log('Axium Server v' + program.version());
|
|
325
|
+
console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
|
|
326
|
+
console.log(styleText('whiteBright', 'Loaded config files:'), config.files.keys().toArray().join(', '));
|
|
327
|
+
process.stdout.write(styleText('whiteBright', 'Database: '));
|
|
328
|
+
const _ = __addDisposableResource(env_4, db.connect(), true);
|
|
329
|
+
try {
|
|
330
|
+
console.log(await db.statusText());
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
output.error('Unavailable');
|
|
334
|
+
}
|
|
335
|
+
console.log(styleText('whiteBright', 'Credentials authentication:'), config.auth.credentials ? styleText('yellow', 'enabled') : 'disabled');
|
|
336
|
+
console.log(styleText('whiteBright', 'Loaded plugins:'), Array.from(plugins)
|
|
337
|
+
.map(plugin => plugin.id)
|
|
338
|
+
.join(', ') || styleText('dim', '(none)'));
|
|
339
|
+
for (const plugin of plugins) {
|
|
340
|
+
if (!plugin.statusText)
|
|
341
|
+
continue;
|
|
342
|
+
console.log(styleText('bold', plugin.name), plugin.version + ':');
|
|
343
|
+
console.log(await plugin.statusText());
|
|
344
|
+
}
|
|
206
345
|
}
|
|
207
|
-
catch {
|
|
208
|
-
|
|
346
|
+
catch (e_4) {
|
|
347
|
+
env_4.error = e_4;
|
|
348
|
+
env_4.hasError = true;
|
|
209
349
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
for (const plugin of plugins) {
|
|
215
|
-
if (!plugin.statusText)
|
|
216
|
-
continue;
|
|
217
|
-
console.log(styleText('bold', plugin.name), plugin.version + ':');
|
|
218
|
-
console.log(await plugin.statusText());
|
|
350
|
+
finally {
|
|
351
|
+
const result_4 = __disposeResources(env_4);
|
|
352
|
+
if (result_4)
|
|
353
|
+
await result_4;
|
|
219
354
|
}
|
|
220
|
-
await db.database.destroy();
|
|
221
355
|
});
|
|
222
356
|
program
|
|
223
357
|
.command('ports')
|
|
@@ -234,7 +368,7 @@ program
|
|
|
234
368
|
.addOption(opts.force)
|
|
235
369
|
.addOption(opts.host)
|
|
236
370
|
.action(async (opt) => {
|
|
237
|
-
config.save({ auth: { secret: randomBytes(32).toString('base64') } }, true);
|
|
371
|
+
/* config.save({ auth: { secret: randomBytes(32).toString('base64') } }, true); */
|
|
238
372
|
await db.init({ ...opt, skip: opt.dbSkip }).catch(handleError);
|
|
239
373
|
await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(handleError);
|
|
240
374
|
});
|