@axium/server 0.27.0 → 0.28.1
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/acl.d.ts +26 -46
- package/dist/acl.js +42 -62
- package/dist/api/acl.js +11 -3
- package/dist/api/admin.js +3 -6
- package/dist/api/metadata.js +4 -11
- package/dist/audit.d.ts +0 -3
- package/dist/audit.js +0 -8
- package/dist/auth.d.ts +10 -5
- package/dist/auth.js +29 -23
- package/dist/cli.d.ts +8 -2
- package/dist/cli.js +18 -606
- package/dist/config.js +6 -5
- package/dist/database.d.ts +413 -29
- package/dist/database.js +522 -247
- package/dist/db.json +71 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +833 -0
- package/package.json +6 -4
- package/routes/account/+page.svelte +11 -13
- package/routes/admin/config/+page.svelte +2 -2
- package/routes/admin/plugins/+page.svelte +41 -4
- package/schemas/config.json +207 -0
- package/schemas/db.json +636 -0
package/dist/acl.d.ts
CHANGED
|
@@ -1,61 +1,41 @@
|
|
|
1
|
-
import type { AccessControl, AccessMap,
|
|
2
|
-
import type
|
|
1
|
+
import type { AccessControl, AccessMap, UserInternal } from '@axium/core';
|
|
2
|
+
import type * as kysely from 'kysely';
|
|
3
|
+
import type { WithRequired } from 'utilium';
|
|
3
4
|
import * as db from './database.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[K in Exclude<keyof db.Schema, `acl.${string}`> as Selectable<db.Schema[K]> extends Omit<Target, 'acl'> ? K : never]: null;
|
|
5
|
+
type _TableNames = keyof {
|
|
6
|
+
[K in keyof db.Schema as db.Schema[K] extends db.DBAccessControl ? K : never]: null;
|
|
7
|
+
};
|
|
8
|
+
type _TargetNames = keyof db.Schema & keyof {
|
|
9
|
+
[K in _TableNames as K extends `acl.${infer TB extends keyof db.Schema}` ? TB : never]: null;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* `never` causes a ton of problems, so we use `string` if none of the tables are shareable.
|
|
13
13
|
*/
|
|
14
|
-
export type
|
|
14
|
+
export type TableName = _TableNames extends never ? keyof db.Schema : _TableNames;
|
|
15
|
+
export type TargetName = _TargetNames extends never ? keyof db.Schema : _TargetNames;
|
|
15
16
|
export interface AccessControlInternal extends AccessControl {
|
|
16
|
-
|
|
17
|
+
tag?: string | null;
|
|
17
18
|
}
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type: string;
|
|
25
|
-
required: true;
|
|
26
|
-
hasDefault: true;
|
|
27
|
-
};
|
|
28
|
-
itemId: {
|
|
29
|
-
type: string;
|
|
30
|
-
required: true;
|
|
31
|
-
};
|
|
32
|
-
permission: {
|
|
33
|
-
type: string;
|
|
34
|
-
required: true;
|
|
35
|
-
hasDefault: true;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Adds an Access Control List (ACL) in the database for managing access to rows in an existing table.
|
|
40
|
-
* @category Plugin API
|
|
41
|
-
*/
|
|
42
|
-
export declare function createTable(table: TargetName): Promise<void>;
|
|
43
|
-
export declare function dropTable(table: TargetName): Promise<void>;
|
|
44
|
-
export declare function wipeTable(table: TargetName): Promise<void>;
|
|
45
|
-
export declare function createEntry(itemType: TargetName, data: Omit<AccessControl, 'createdAt'>): Promise<AccessControlInternal>;
|
|
46
|
-
export declare function deleteEntry(itemType: TargetName, itemId: string, userId: string): Promise<void>;
|
|
19
|
+
export type PermissionsFor<TB extends TableName> = Omit<kysely.Selectable<db.Schema[TB]>, keyof AccessControlInternal | number | symbol>;
|
|
20
|
+
export type Result<TB extends TableName> = AccessControlInternal & PermissionsFor<TB>;
|
|
21
|
+
export type WithACL<TB extends TargetName> = kysely.Selectable<db.Schema[TB]> & {
|
|
22
|
+
userId: string;
|
|
23
|
+
acl: Result<`acl.${TB}`>[];
|
|
24
|
+
} & Record<string, any>;
|
|
47
25
|
export interface ACLSelectionOptions {
|
|
48
|
-
/** If specified,
|
|
49
|
-
|
|
26
|
+
/** If specified, files by user UUID */
|
|
27
|
+
user?: Pick<UserInternal, 'id' | 'roles' | 'tags'>;
|
|
50
28
|
/** Instead of using the `id` from `table`, use the `id` from this instead */
|
|
51
29
|
alias?: string;
|
|
52
30
|
}
|
|
53
31
|
/**
|
|
54
32
|
* Helper to select all access controls for a given table, including the user information.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
33
|
+
* Optionally filter for the entries applicable to a specific user.
|
|
34
|
+
* This includes entries matching the user's ID, roles, or tags along with the "public" entry where all three "target" columns are null.
|
|
57
35
|
*/
|
|
58
|
-
export declare function from(table:
|
|
59
|
-
export declare function get(
|
|
60
|
-
export declare function set(
|
|
36
|
+
export declare function from<const TB extends TargetName>(table: TB, opt?: ACLSelectionOptions): (eb: kysely.ExpressionBuilder<db.Schema, any>) => kysely.AliasedRawBuilder<Result<`acl.${TB}`>[], 'acl'>;
|
|
37
|
+
export declare function get<const TB extends TableName>(table: TB, itemId: string): Promise<WithRequired<AccessControlInternal & kysely.Selectable<db.Schema[TB]>, 'user'>[]>;
|
|
38
|
+
export declare function set<const TB extends TableName>(table: TB, itemId: string, data: AccessMap): Promise<(AccessControlInternal & kysely.Selectable<db.Schema[TB]>)[]>;
|
|
39
|
+
export declare function check<const TB extends TableName>(acl: Result<TB>[], permissions: Partial<PermissionsFor<TB>>): Set<keyof PermissionsFor<TB>>;
|
|
40
|
+
export declare function listTables(): Record<string, TableName>;
|
|
61
41
|
export {};
|
package/dist/acl.js
CHANGED
|
@@ -1,84 +1,64 @@
|
|
|
1
|
-
import * as io from '@axium/core/node/io';
|
|
2
|
-
import { sql } from 'kysely';
|
|
3
1
|
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
|
4
2
|
import * as db from './database.js';
|
|
5
|
-
const accessControllableTypes = {
|
|
6
|
-
userId: { type: 'uuid', required: true },
|
|
7
|
-
publicPermission: { type: 'int4', required: true, hasDefault: true },
|
|
8
|
-
};
|
|
9
|
-
export const expectedTypes = {
|
|
10
|
-
userId: { type: 'uuid', required: true },
|
|
11
|
-
createdAt: { type: 'timestamptz', required: true, hasDefault: true },
|
|
12
|
-
itemId: { type: 'uuid', required: true },
|
|
13
|
-
permission: { type: 'int4', required: true, hasDefault: true },
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Adds an Access Control List (ACL) in the database for managing access to rows in an existing table.
|
|
17
|
-
* @category Plugin API
|
|
18
|
-
*/
|
|
19
|
-
export async function createTable(table) {
|
|
20
|
-
await db.checkTableTypes(table, accessControllableTypes, { strict: true, extra: false });
|
|
21
|
-
io.start(`Creating table acl.${table}`);
|
|
22
|
-
await db.database.schema
|
|
23
|
-
.createTable(`acl.${table}`)
|
|
24
|
-
.addColumn('userId', 'uuid', col => col.references('users.id').onDelete('cascade'))
|
|
25
|
-
.addColumn('itemId', 'uuid', col => col.references(`${table}.id`).onDelete('cascade'))
|
|
26
|
-
.addPrimaryKeyConstraint('PK_acl_' + table, ['userId', 'itemId'])
|
|
27
|
-
.addColumn('createdAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
|
|
28
|
-
.addColumn('permission', 'integer', col => col.notNull().check(sql `permission >= 0 AND permission <= 5`))
|
|
29
|
-
.execute()
|
|
30
|
-
.then(io.done)
|
|
31
|
-
.catch(db.warnExists);
|
|
32
|
-
await db.createIndex(`acl.${table}`, 'userId');
|
|
33
|
-
await db.createIndex(`acl.${table}`, 'itemId');
|
|
34
|
-
}
|
|
35
|
-
export async function dropTable(table) {
|
|
36
|
-
io.start(`Dropping table acl.${table}`);
|
|
37
|
-
await db.database.schema.dropTable(`acl.${table}`).execute().then(io.done).catch(db.warnExists);
|
|
38
|
-
}
|
|
39
|
-
export async function wipeTable(table) {
|
|
40
|
-
io.start(`Wiping table acl.${table}`);
|
|
41
|
-
await db.database.deleteFrom(`acl.${table}`).execute().then(io.done).catch(db.warnExists);
|
|
42
|
-
}
|
|
43
|
-
export async function createEntry(itemType, data) {
|
|
44
|
-
return await db.database.insertInto(`acl.${itemType}`).values(data).returningAll().executeTakeFirstOrThrow();
|
|
45
|
-
}
|
|
46
|
-
export async function deleteEntry(itemType, itemId, userId) {
|
|
47
|
-
await db.database.deleteFrom(`acl.${itemType}`).where('itemId', '=', itemId).where('userId', '=', userId).execute();
|
|
48
|
-
}
|
|
49
3
|
/**
|
|
50
4
|
* Helper to select all access controls for a given table, including the user information.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
5
|
+
* Optionally filter for the entries applicable to a specific user.
|
|
6
|
+
* This includes entries matching the user's ID, roles, or tags along with the "public" entry where all three "target" columns are null.
|
|
53
7
|
*/
|
|
54
8
|
export function from(table, opt = {}) {
|
|
55
9
|
return (eb) => jsonArrayFrom(eb
|
|
56
10
|
.selectFrom(`acl.${table} as _acl`)
|
|
57
11
|
.selectAll()
|
|
58
|
-
.select(db.userFromId)
|
|
59
12
|
.whereRef(`_acl.itemId`, '=', `${opt.alias || table}.id`)
|
|
60
|
-
|
|
13
|
+
.where(eb => {
|
|
14
|
+
const allNull = eb.and([eb('userId', 'is', null), eb('role', 'is', null), eb('tag', 'is', null)]);
|
|
15
|
+
if (!opt.user)
|
|
16
|
+
return allNull;
|
|
17
|
+
const ors = [allNull, eb('userId', '=', opt.user.id)];
|
|
18
|
+
if (opt.user.roles.length)
|
|
19
|
+
ors.push(eb('role', 'in', opt.user.roles));
|
|
20
|
+
if (opt.user.tags.length)
|
|
21
|
+
ors.push(eb('tag', 'in', opt.user.tags));
|
|
22
|
+
return eb.or(ors);
|
|
23
|
+
}))
|
|
61
24
|
.$castTo()
|
|
62
25
|
.as('acl');
|
|
63
26
|
}
|
|
64
|
-
export async function get(
|
|
65
|
-
|
|
27
|
+
export async function get(table, itemId) {
|
|
28
|
+
// @ts-expect-error 2349
|
|
29
|
+
return await db.database.selectFrom(table).where('itemId', '=', itemId).selectAll().select(db.userFromId).execute();
|
|
66
30
|
}
|
|
67
|
-
export async function set(
|
|
68
|
-
|
|
69
|
-
// @ts-expect-error 2353 - TS misses the column
|
|
70
|
-
await db.database.updateTable(itemType).set({ publicPermission: data.public }).where('id', '=', itemId).execute();
|
|
71
|
-
delete data.public;
|
|
72
|
-
}
|
|
73
|
-
const entries = Object.entries(data).map(([userId, perm]) => ({ userId, perm }));
|
|
31
|
+
export async function set(table, itemId, data) {
|
|
32
|
+
const entries = Object.entries(data).map(([userId, perm]) => ({ userId, ...perm }));
|
|
74
33
|
if (!entries.length)
|
|
75
34
|
return [];
|
|
76
35
|
return await db.database
|
|
77
|
-
.updateTable(
|
|
36
|
+
.updateTable(table)
|
|
37
|
+
// @ts-expect-error 2349
|
|
78
38
|
.from(db.values(entries, 'data'))
|
|
79
|
-
.set(
|
|
80
|
-
.whereRef(
|
|
39
|
+
.set()
|
|
40
|
+
.whereRef(`${table}.userId`, '=', 'data.userId')
|
|
81
41
|
.where('itemId', '=', itemId)
|
|
82
42
|
.returningAll()
|
|
83
43
|
.execute();
|
|
84
44
|
}
|
|
45
|
+
export function check(acl, permissions) {
|
|
46
|
+
const allowed = new Set();
|
|
47
|
+
const all = new Set(Object.keys(permissions));
|
|
48
|
+
const entries = Object.entries(permissions);
|
|
49
|
+
for (const control of acl) {
|
|
50
|
+
for (const [key, needed] of entries) {
|
|
51
|
+
const value = control[key];
|
|
52
|
+
if (value === needed)
|
|
53
|
+
allowed.add(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return all.difference(allowed);
|
|
57
|
+
}
|
|
58
|
+
export function listTables() {
|
|
59
|
+
const tables = {};
|
|
60
|
+
for (const [, file] of db.getSchemaFiles()) {
|
|
61
|
+
Object.assign(tables, file.acl_tables || {});
|
|
62
|
+
}
|
|
63
|
+
return tables;
|
|
64
|
+
}
|
package/dist/api/acl.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AccessMap } from '@axium/core/access';
|
|
2
2
|
import * as z from 'zod';
|
|
3
3
|
import * as acl from '../acl.js';
|
|
4
|
-
import { parseBody, withError } from '../requests.js';
|
|
4
|
+
import { error, parseBody, withError } from '../requests.js';
|
|
5
5
|
import { addRoute } from '../routes.js';
|
|
6
|
+
import { checkAuthForItem } from '../auth.js';
|
|
6
7
|
addRoute({
|
|
7
8
|
path: '/api/acl/:itemType/:itemId',
|
|
8
9
|
params: {
|
|
@@ -10,10 +11,17 @@ addRoute({
|
|
|
10
11
|
itemId: z.uuid(),
|
|
11
12
|
},
|
|
12
13
|
async GET(request, { itemType, itemId }) {
|
|
13
|
-
|
|
14
|
+
const tables = acl.listTables();
|
|
15
|
+
if (!(itemType in tables))
|
|
16
|
+
error(400, 'Invalid item type: ' + itemType);
|
|
17
|
+
return await acl.get(tables[itemType], itemId).catch(withError('Failed to get access controls'));
|
|
14
18
|
},
|
|
15
19
|
async POST(request, { itemType, itemId }) {
|
|
16
20
|
const data = await parseBody(request, AccessMap);
|
|
17
|
-
|
|
21
|
+
const tables = acl.listTables();
|
|
22
|
+
if (!(itemType in tables))
|
|
23
|
+
error(400, 'Invalid item type: ' + itemType);
|
|
24
|
+
await checkAuthForItem(request, itemType, itemId, { manage: true });
|
|
25
|
+
return await acl.set(tables[itemType], itemId, data);
|
|
18
26
|
},
|
|
19
27
|
});
|
package/dist/api/admin.js
CHANGED
|
@@ -5,16 +5,13 @@ import { omit } from 'utilium';
|
|
|
5
5
|
import * as z from 'zod';
|
|
6
6
|
import pkg from '../../package.json' with { type: 'json' };
|
|
7
7
|
import { audit, events, getEvents } from '../audit.js';
|
|
8
|
-
import {
|
|
8
|
+
import { requireSession } from '../auth.js';
|
|
9
9
|
import { config } from '../config.js';
|
|
10
10
|
import { count, database as db } from '../database.js';
|
|
11
|
-
import { error,
|
|
11
|
+
import { error, withError } from '../requests.js';
|
|
12
12
|
import { addRoute } from '../routes.js';
|
|
13
13
|
async function assertAdmin(route, req) {
|
|
14
|
-
const
|
|
15
|
-
if (!token)
|
|
16
|
-
error(401, 'Missing token');
|
|
17
|
-
const admin = await getSessionAndUser(token).catch(withError('Invalid session', 400));
|
|
14
|
+
const admin = await requireSession(req);
|
|
18
15
|
if (!admin.user.isAdmin)
|
|
19
16
|
error(403, 'Not an administrator');
|
|
20
17
|
if (!config.admin_api)
|
package/dist/api/metadata.js
CHANGED
|
@@ -2,24 +2,17 @@ import { apps } from '@axium/core';
|
|
|
2
2
|
import { plugins } from '@axium/core/plugins';
|
|
3
3
|
import { requestMethods } from '@axium/core/requests';
|
|
4
4
|
import pkg from '../../package.json' with { type: 'json' };
|
|
5
|
-
import {
|
|
5
|
+
import { requireSession } from '../auth.js';
|
|
6
6
|
import { config } from '../config.js';
|
|
7
|
-
import { error
|
|
7
|
+
import { error } from '../requests.js';
|
|
8
8
|
import { addRoute, routes } from '../routes.js';
|
|
9
9
|
addRoute({
|
|
10
10
|
path: '/api/metadata',
|
|
11
11
|
async GET(request) {
|
|
12
12
|
if (!config.debug) {
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
15
|
-
error(401, 'Missing session token');
|
|
16
|
-
const session = await getSessionAndUser(token);
|
|
17
|
-
if (!session)
|
|
18
|
-
error(401, 'Invalid session');
|
|
19
|
-
if (!session.user.isAdmin)
|
|
13
|
+
const { user } = await requireSession(request);
|
|
14
|
+
if (!user.isAdmin)
|
|
20
15
|
error(403, 'User is not an administrator');
|
|
21
|
-
if (session.user.isSuspended)
|
|
22
|
-
error(403, 'User is suspended');
|
|
23
16
|
}
|
|
24
17
|
return {
|
|
25
18
|
version: pkg.version,
|
package/dist/audit.d.ts
CHANGED
package/dist/audit.js
CHANGED
|
@@ -105,14 +105,6 @@ addEvent({
|
|
|
105
105
|
tags: ['cli'],
|
|
106
106
|
extra: { user: z.string() },
|
|
107
107
|
});
|
|
108
|
-
addEvent({
|
|
109
|
-
source: '@axium/server',
|
|
110
|
-
name: 'acl_id_mismatch',
|
|
111
|
-
severity: Severity.Critical,
|
|
112
|
-
tags: ['acl', 'auth'],
|
|
113
|
-
extra: { item: z.string() },
|
|
114
|
-
noAutoSuspend: true,
|
|
115
|
-
});
|
|
116
108
|
addEvent({
|
|
117
109
|
source: '@axium/server',
|
|
118
110
|
name: 'admin_api',
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Passkey,
|
|
2
|
-
import type { Insertable } from 'kysely';
|
|
1
|
+
import type { Passkey, Session, UserInternal, Verification } from '@axium/core';
|
|
2
|
+
import type { Insertable, Selectable } from 'kysely';
|
|
3
3
|
import * as acl from './acl.js';
|
|
4
4
|
import { type Schema } from './database.js';
|
|
5
5
|
export declare function getUser(id: string): Promise<UserInternal>;
|
|
@@ -13,6 +13,7 @@ export interface SessionAndUser extends SessionInternal {
|
|
|
13
13
|
}
|
|
14
14
|
export declare function getSessionAndUser(token: string): Promise<SessionAndUser>;
|
|
15
15
|
export declare function getSession(sessionId: string): Promise<SessionInternal>;
|
|
16
|
+
export declare function requireSession(request: Request, sensitive?: boolean): Promise<SessionAndUser>;
|
|
16
17
|
export declare function getSessions(userId: string): Promise<SessionInternal[]>;
|
|
17
18
|
export type VerificationRole = 'verify_email' | 'login';
|
|
18
19
|
export interface VerificationInternal extends Verification {
|
|
@@ -38,10 +39,14 @@ export interface UserAuthResult extends SessionAndUser {
|
|
|
38
39
|
accessor: UserInternal;
|
|
39
40
|
}
|
|
40
41
|
export declare function checkAuthForUser(request: Request, userId: string, sensitive?: boolean): Promise<UserAuthResult>;
|
|
41
|
-
export interface ItemAuthResult<
|
|
42
|
+
export interface ItemAuthResult<TB extends acl.TargetName> {
|
|
42
43
|
fromACL: boolean;
|
|
43
|
-
item:
|
|
44
|
+
item: Selectable<Schema[TB]>;
|
|
44
45
|
user?: UserInternal;
|
|
45
46
|
session?: SessionInternal;
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Authenticate a request against an "item" which has an ACL table.
|
|
50
|
+
* This will fetch the item, ACLs, users, and the authenticating session.
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkAuthForItem<const TB extends acl.TargetName>(request: Request, itemType: TB, itemId: string, permissions: Partial<acl.PermissionsFor<`acl.${TB}`>>): Promise<ItemAuthResult<TB>>;
|
package/dist/auth.js
CHANGED
|
@@ -46,6 +46,15 @@ export async function getSession(sessionId) {
|
|
|
46
46
|
.where('sessions.expires', '>', new Date())
|
|
47
47
|
.executeTakeFirstOrThrow();
|
|
48
48
|
}
|
|
49
|
+
export async function requireSession(request, sensitive = false) {
|
|
50
|
+
const token = getToken(request, sensitive);
|
|
51
|
+
if (!token)
|
|
52
|
+
error(401, 'Missing session token');
|
|
53
|
+
const session = await getSessionAndUser(token).catch(withError('Invalid or expired session token', 401));
|
|
54
|
+
if (session.user.isSuspended)
|
|
55
|
+
error(403, 'User is suspended');
|
|
56
|
+
return session;
|
|
57
|
+
}
|
|
49
58
|
export async function getSessions(userId) {
|
|
50
59
|
return await db.selectFrom('sessions').selectAll().where('userId', '=', userId).where('sessions.expires', '>', new Date()).execute();
|
|
51
60
|
}
|
|
@@ -88,12 +97,7 @@ export async function updatePasskeyCounter(id, newCounter) {
|
|
|
88
97
|
return passkey;
|
|
89
98
|
}
|
|
90
99
|
export async function checkAuthForUser(request, userId, sensitive = false) {
|
|
91
|
-
const
|
|
92
|
-
if (!token)
|
|
93
|
-
throw error(401, 'Missing token');
|
|
94
|
-
const session = await getSessionAndUser(token).catch(withError('Invalid or expired session', 401));
|
|
95
|
-
if (session.user.isSuspended)
|
|
96
|
-
error(403, 'User is suspended');
|
|
100
|
+
const session = await requireSession(request);
|
|
97
101
|
if (session.userId !== userId) {
|
|
98
102
|
if (!session.user?.isAdmin)
|
|
99
103
|
error(403, 'User ID mismatch');
|
|
@@ -106,42 +110,44 @@ export async function checkAuthForUser(request, userId, sensitive = false) {
|
|
|
106
110
|
error(403, 'This token can not be used for sensitive actions');
|
|
107
111
|
return Object.assign(session, { accessor: session.user });
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Authenticate a request against an "item" which has an ACL table.
|
|
115
|
+
* This will fetch the item, ACLs, users, and the authenticating session.
|
|
116
|
+
*/
|
|
117
|
+
export async function checkAuthForItem(request, itemType, itemId, permissions) {
|
|
110
118
|
const token = getToken(request, false);
|
|
111
119
|
if (!token)
|
|
112
120
|
error(401, 'Missing token');
|
|
113
121
|
const session = await getSessionAndUser(token).catch(() => null);
|
|
122
|
+
const { userId, user } = session ?? {};
|
|
114
123
|
const item = await db
|
|
115
124
|
.selectFrom(itemType)
|
|
116
125
|
.selectAll()
|
|
117
126
|
.where('id', '=', itemId)
|
|
118
|
-
.$if(!!
|
|
127
|
+
.$if(!!userId, eb => eb.select(acl.from(itemType, { user })))
|
|
119
128
|
.$castTo()
|
|
120
129
|
.executeTakeFirstOrThrow()
|
|
121
|
-
.catch(
|
|
130
|
+
.catch(e => {
|
|
131
|
+
if (e.message.includes('no rows'))
|
|
132
|
+
error(404, itemType + ' not found');
|
|
133
|
+
throw e;
|
|
134
|
+
});
|
|
122
135
|
const result = {
|
|
123
136
|
session: session ? omit(session, 'user') : undefined,
|
|
124
137
|
item: omit(item, 'acl'),
|
|
125
|
-
user
|
|
138
|
+
user,
|
|
126
139
|
fromACL: false,
|
|
127
140
|
};
|
|
128
|
-
if (
|
|
129
|
-
return result;
|
|
130
|
-
if (!session)
|
|
141
|
+
if (!session || !user)
|
|
131
142
|
error(403, 'Access denied');
|
|
132
|
-
if (
|
|
143
|
+
if (user.isSuspended)
|
|
133
144
|
error(403, 'User is suspended');
|
|
134
|
-
if (
|
|
145
|
+
if (userId == item.userId)
|
|
135
146
|
return result;
|
|
136
147
|
result.fromACL = true;
|
|
137
148
|
if (!item.acl || !item.acl.length)
|
|
138
149
|
error(403, 'Access denied');
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
error(500, 'Access control entry does not match session user');
|
|
143
|
-
}
|
|
144
|
-
if (control.permission >= permission)
|
|
145
|
-
return result;
|
|
146
|
-
error(403, 'Access denied');
|
|
150
|
+
if (acl.check(item.acl, permissions).size)
|
|
151
|
+
error(403, 'Access denied');
|
|
152
|
+
return result;
|
|
147
153
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import type { UserInternal } from '@axium/core';
|
|
2
|
+
export declare function userText(user: UserInternal, bold?: boolean): string;
|
|
3
|
+
export declare function lookupUser(lookup: string): Promise<UserInternal>;
|
|
4
|
+
/**
|
|
5
|
+
* Updates an array of strings by adding or removing items.
|
|
6
|
+
* Only returns whether the array was updated and diff text for what actually changed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function diffUpdate(original: string[], add?: string[], remove?: string[]): [updated: boolean, newValue: string[], diffText: string];
|