@axium/server 0.35.2 → 0.36.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/acl.d.ts +16 -2
- package/dist/acl.js +24 -0
- package/dist/api/acl.js +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +3 -4
- package/dist/main.js +16 -0
- package/package.json +1 -1
package/dist/acl.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AccessControl, type AccessTarget, type UserInternal } from '@axium/core';
|
|
1
|
+
import { type AccessControl, type AccessControllable, type AccessTarget, type UserInternal } from '@axium/core';
|
|
2
2
|
import type * as kysely from 'kysely';
|
|
3
3
|
import type { WithRequired } from 'utilium';
|
|
4
4
|
import * as db from './database.js';
|
|
@@ -6,7 +6,7 @@ type _TableNames = keyof {
|
|
|
6
6
|
[K in keyof db.Schema as db.Schema[K] extends db.DBAccessControl ? K : never]: null;
|
|
7
7
|
};
|
|
8
8
|
type _TargetNames = keyof db.Schema & keyof {
|
|
9
|
-
[K in
|
|
9
|
+
[K in keyof db.Schema as db.Schema[K] extends AccessControllable ? (`acl.${K}` extends keyof db.Schema ? K : never) : 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.
|
|
@@ -40,4 +40,18 @@ export declare function remove<const TB extends TableName>(table: TB, itemId: st
|
|
|
40
40
|
export declare function add<const TB extends TableName>(table: TB, itemId: string, target: AccessTarget): Promise<Result<TB>>;
|
|
41
41
|
export declare function check<const TB extends TableName>(acl: Result<TB>[], permissions: Partial<PermissionsFor<TB>>): Set<keyof PermissionsFor<TB>>;
|
|
42
42
|
export declare function listTables(): Record<string, TableName>;
|
|
43
|
+
export interface OptionsForWhere<TB extends TargetName> {
|
|
44
|
+
itemId?: keyof db.Schema[TB] & string;
|
|
45
|
+
/** Alias for the item table/value */
|
|
46
|
+
alias?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Use in a `where` to filter by items a user can access because of an ACL entry.
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
export declare function existsIn<const TB extends TargetName, const O extends OptionsForWhere<TB>>(table: TB, user: Pick<UserInternal, 'id' | 'roles' | 'tags'>, options?: O): (eb: kysely.ExpressionBuilder<db.Schema & { [K in O["alias"] extends string ? O["alias"] : never]: db.Schema[TB]; }, O["alias"] extends string ? O["alias"] & (keyof db.Schema | (O["alias"] extends string ? O["alias"] : never)) : TB>) => kysely.ExpressionWrapper<db.Schema & { [K in O["alias"] extends string ? O["alias"] : never]: db.Schema[TB]; }, O["alias"] extends string ? O["alias"] & (keyof db.Schema | (O["alias"] extends string ? O["alias"] : never)) : TB, kysely.SqlBool>;
|
|
53
|
+
/**
|
|
54
|
+
* Use in a `where` to filter by items a user has access to
|
|
55
|
+
*/
|
|
56
|
+
export declare function userHasAccess<const TB extends TargetName>(table: TB, user: Pick<UserInternal, 'id' | 'roles' | 'tags'>, options?: OptionsForWhere<TB>): (eb: kysely.ExpressionBuilder<db.Schema, TB>) => kysely.ExpressionWrapper<db.Schema, TB, kysely.SqlBool>;
|
|
43
57
|
export {};
|
package/dist/acl.js
CHANGED
|
@@ -82,3 +82,27 @@ export function listTables() {
|
|
|
82
82
|
}
|
|
83
83
|
return tables;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Use in a `where` to filter by items a user can access because of an ACL entry.
|
|
87
|
+
*
|
|
88
|
+
*/
|
|
89
|
+
export function existsIn(table, user, options = {}) {
|
|
90
|
+
return (eb) => eb.exists(eb
|
|
91
|
+
.selectFrom(`acl.${table}`)
|
|
92
|
+
// @ts-expect-error 2349
|
|
93
|
+
.whereRef('itemId', '=', `${options.alias || `public.${table}`}.${options.itemId || 'id'}`)
|
|
94
|
+
.where((eb) => {
|
|
95
|
+
const ors = [eb('userId', '=', user.id)];
|
|
96
|
+
if (user.roles.length)
|
|
97
|
+
ors.push(eb('role', 'in', user.roles));
|
|
98
|
+
if (user.tags.length)
|
|
99
|
+
ors.push(eb('tag', 'in', user.tags));
|
|
100
|
+
return eb.or(ors);
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Use in a `where` to filter by items a user has access to
|
|
105
|
+
*/
|
|
106
|
+
export function userHasAccess(table, user, options = {}) {
|
|
107
|
+
return (eb) => eb.or([eb('userId', '=', user.id), existsIn(table, user, options)(eb)]);
|
|
108
|
+
}
|
package/dist/api/acl.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { AccessControlUpdate, AccessTarget } from '@axium/core';
|
|
1
2
|
import * as z from 'zod';
|
|
2
3
|
import * as acl from '../acl.js';
|
|
4
|
+
import { authRequestForItem } from '../auth.js';
|
|
3
5
|
import { error, parseBody, withError } from '../requests.js';
|
|
4
6
|
import { addRoute } from '../routes.js';
|
|
5
|
-
import { authRequestForItem } from '../auth.js';
|
|
6
|
-
import { AccessControlUpdate, AccessTarget } from '@axium/core';
|
|
7
7
|
function getTable(itemType) {
|
|
8
8
|
const tables = acl.listTables();
|
|
9
9
|
if (!(itemType in tables))
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Passkey, Session, UserInternal, VerificationInternal, VerificationRole } from '@axium/core';
|
|
2
|
-
import type { Insertable, Kysely
|
|
2
|
+
import type { Insertable, Kysely } from 'kysely';
|
|
3
3
|
import { type WithRequired } from 'utilium';
|
|
4
4
|
import * as acl from './acl.js';
|
|
5
5
|
import { type Schema } from './database.js';
|
|
@@ -37,7 +37,7 @@ export interface UserAuthResult extends SessionAndUser {
|
|
|
37
37
|
export declare function checkAuthForUser(request: Request, userId: string, sensitive?: boolean): Promise<UserAuthResult>;
|
|
38
38
|
export interface ItemAuthResult<TB extends acl.TargetName> {
|
|
39
39
|
fromACL: boolean;
|
|
40
|
-
item:
|
|
40
|
+
item: acl.WithACL<TB>;
|
|
41
41
|
user?: UserInternal;
|
|
42
42
|
session?: SessionInternal;
|
|
43
43
|
}
|
package/dist/auth.js
CHANGED
|
@@ -113,21 +113,20 @@ export async function checkAuthForUser(request, userId, sensitive = false) {
|
|
|
113
113
|
export async function authSessionForItem(itemType, itemId, permissions, session) {
|
|
114
114
|
const { userId, user } = session ?? {};
|
|
115
115
|
// Note: we need to do casting because of TS limitations with generics
|
|
116
|
-
const item = await db
|
|
116
|
+
const item = (await db
|
|
117
117
|
.selectFrom(itemType)
|
|
118
118
|
.selectAll()
|
|
119
119
|
.where('id', '=', itemId)
|
|
120
120
|
.$if(!!userId, eb => eb.select(acl.from(itemType, { user })))
|
|
121
|
-
.$castTo()
|
|
122
121
|
.executeTakeFirstOrThrow()
|
|
123
122
|
.catch(e => {
|
|
124
123
|
if (e.message.includes('no rows'))
|
|
125
124
|
error(404, itemType + ' not found');
|
|
126
125
|
throw e;
|
|
127
|
-
});
|
|
126
|
+
}));
|
|
128
127
|
const result = {
|
|
129
128
|
session: session ? omit(session, 'user') : undefined,
|
|
130
|
-
item
|
|
129
|
+
item,
|
|
131
130
|
user,
|
|
132
131
|
fromACL: false,
|
|
133
132
|
};
|
package/dist/main.js
CHANGED
|
@@ -590,6 +590,22 @@ try {
|
|
|
590
590
|
if (!plugin)
|
|
591
591
|
io.exit(`Can't find a plugin matching "${search}"`);
|
|
592
592
|
const _ = __addDisposableResource(env_4, db.connect(), true);
|
|
593
|
+
const info = db.getUpgradeInfo();
|
|
594
|
+
const exclude = Object.keys(info.current);
|
|
595
|
+
if (exclude.includes(plugin.name))
|
|
596
|
+
io.exit('Plugin is already initialized (database)');
|
|
597
|
+
const schema = db.getFullSchema({ exclude });
|
|
598
|
+
const delta = db.computeDelta({ tables: {}, indexes: [] }, schema);
|
|
599
|
+
if (db.deltaIsEmpty(delta)) {
|
|
600
|
+
io.info('Plugin does not define any database schema.');
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
for (const text of db.displayDelta(delta))
|
|
604
|
+
console.log(text);
|
|
605
|
+
await rlConfirm();
|
|
606
|
+
await db.applyDelta(delta);
|
|
607
|
+
Object.assign(info.current, schema.versions);
|
|
608
|
+
db.setUpgradeInfo(info);
|
|
593
609
|
}
|
|
594
610
|
catch (e_4) {
|
|
595
611
|
env_4.error = e_4;
|