@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 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 _TableNames as K extends `acl.${infer TB extends keyof db.Schema}` ? TB : never]: null;
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, Selectable } from '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: Selectable<Schema[TB]>;
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: omit(item, 'acl'),
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.35.2",
3
+ "version": "0.36.0",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",