@lobb-js/lobb-ext-auth 0.15.1 → 0.16.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/index.js CHANGED
@@ -3,7 +3,6 @@ import { onStartup, onRouteChange } from "./onStartup";
3
3
  import LoginPage from "./lib/components/pages/loginPage/index.svelte";
4
4
  import UserSettings from "./lib/components/pages/userSettings/index.svelte";
5
5
  import Settings from "./lib/components/pages/settings/index.svelte";
6
- import { isActionAllowed } from "./shared/permissions";
7
6
  // TODO we should export the extension object directly without a function at all. because we dont set configuration in here
8
7
  export default function extension(utils) {
9
8
  return {
@@ -44,7 +43,10 @@ export default function extension(utils) {
44
43
  ? input.collection
45
44
  : input.collection ? [input.collection] : [];
46
45
  for (const collectionName of requiredCollections) {
47
- if (!isActionAllowed(collectionName, action, permissions)) {
46
+ const collPerm = permissions[collectionName];
47
+ if (collPerm === true)
48
+ continue;
49
+ if (!collPerm || collPerm[action] !== true) {
48
50
  return ctx.takeOver(false);
49
51
  }
50
52
  }
@@ -4,10 +4,10 @@ export const activityFeedCollection: CollectionConfig = {
4
4
  indexes: {},
5
5
  fields: {
6
6
  id: {
7
- type: "integer",
7
+ type: "id",
8
8
  },
9
9
  user_id: {
10
- type: "integer",
10
+ type: "id",
11
11
  references: {
12
12
  collection: "auth_users",
13
13
  field: "id",
@@ -31,8 +31,7 @@ export const activityFeedCollection: CollectionConfig = {
31
31
  required: true,
32
32
  },
33
33
  item_id: {
34
- type: "string",
35
- length: 255,
34
+ type: "id",
36
35
  required: true,
37
36
  },
38
37
  created_at: {
@@ -4,10 +4,10 @@ export const sessionsCollection: CollectionConfig = {
4
4
  indexes: {},
5
5
  fields: {
6
6
  id: {
7
- type: "integer",
7
+ type: "id",
8
8
  },
9
9
  user_id: {
10
- type: "integer",
10
+ type: "id",
11
11
  references: {
12
12
  collection: "auth_users",
13
13
  field: "id",
@@ -10,7 +10,7 @@ export const sharesCollection: CollectionConfig = {
10
10
  indexes: {},
11
11
  fields: {
12
12
  id: {
13
- type: "integer",
13
+ type: "id",
14
14
  },
15
15
  // Auto-generated server-side by the auth_generateShareToken workflow on
16
16
  // create — clients should not supply this; the generated token comes
@@ -49,7 +49,7 @@ export const sharesCollection: CollectionConfig = {
49
49
  ui: { hidden: true },
50
50
  },
51
51
  created_by: {
52
- type: "integer",
52
+ type: "id",
53
53
  references: {
54
54
  collection: "auth_users",
55
55
  field: "id",
@@ -14,7 +14,7 @@ export const usersCollection: NormalCollectionConfig = {
14
14
  },
15
15
  fields: {
16
16
  id: {
17
- type: "integer",
17
+ type: "id",
18
18
  },
19
19
  email: {
20
20
  type: "string",
@@ -37,7 +37,7 @@ export const usersCollection: NormalCollectionConfig = {
37
37
  },
38
38
  role: {
39
39
  type: "string",
40
- length: 255,
40
+ length: 100,
41
41
  required: true,
42
42
  },
43
43
  },
@@ -1,4 +1,4 @@
1
- import type { CollectionConfig } from "@lobb-js/core";
1
+ import type { CollectionConfig, CollectionField } from "@lobb-js/core";
2
2
  import type { CreatePermissionAction } from "./permissionsAction/create.ts";
3
3
  import type { ReadPermissionAction } from "./permissionsAction/read.ts";
4
4
  import type { UpdatePermissionAction } from "./permissionsAction/update.ts";
@@ -33,7 +33,7 @@ export type ExtensionConfig = {
33
33
  roles?: Record<string, RolesConfig | undefined>;
34
34
  extend_users?: {
35
35
  indexes?: CollectionConfig["indexes"];
36
- fields?: Omit<CollectionConfig["fields"], "id">;
36
+ fields?: Record<string, CollectionField>;
37
37
  };
38
38
  studio?: {
39
39
  publicPaths?: string[];
@@ -1,4 +1,4 @@
1
- import type { CollectionConfig } from "@lobb-js/core";
1
+ import type { CollectionConfig, CollectionField } from "@lobb-js/core";
2
2
  import type { CreatePermissionAction } from "./permissionsAction/create.ts";
3
3
  import type { ReadPermissionAction } from "./permissionsAction/read.ts";
4
4
  import type { UpdatePermissionAction } from "./permissionsAction/update.ts";
@@ -43,7 +43,7 @@ export type ExtensionConfig = {
43
43
  roles?: Record<string, RolesConfig | undefined>;
44
44
  extend_users?: {
45
45
  indexes?: CollectionConfig["indexes"];
46
- fields?: Omit<CollectionConfig["fields"], "id">;
46
+ fields?: Record<string, CollectionField>;
47
47
  };
48
48
  studio?: {
49
49
  publicPaths?: string[];
@@ -4,7 +4,6 @@ import { onStartup, onRouteChange } from "./onStartup";
4
4
  import LoginPage from "./lib/components/pages/loginPage/index.svelte";
5
5
  import UserSettings from "./lib/components/pages/userSettings/index.svelte";
6
6
  import Settings from "./lib/components/pages/settings/index.svelte";
7
- import { isActionAllowed } from "./shared/permissions";
8
7
  import type { CollectionPermissionActionsKeys } from "../config/extensionConfigSchema";
9
8
 
10
9
  // TODO we should export the extension object directly without a function at all. because we dont set configuration in here
@@ -48,7 +47,9 @@ export default function extension(utils: ExtensionUtils): Extension {
48
47
  : input.collection ? [input.collection] : [];
49
48
 
50
49
  for (const collectionName of requiredCollections) {
51
- if (!isActionAllowed(collectionName, action, permissions)) {
50
+ const collPerm = permissions[collectionName];
51
+ if (collPerm === true) continue;
52
+ if (!collPerm || collPerm[action] !== true) {
52
53
  return ctx.takeOver(false);
53
54
  }
54
55
  }
@@ -3,7 +3,23 @@ import type { Context } from "hono";
3
3
  import type { ExtensionConfig } from "../config/extensionConfigSchema.ts";
4
4
  import { getBearerToken } from "../utils.ts";
5
5
 
6
- export function getBaseWorkflows(_extensionConfig: ExtensionConfig): Workflow[] {
6
+ // The admin user is identified by the configured admin email — its id is a uuid
7
+ // now, no longer a predictable "1". Looks the target user up and compares.
8
+ async function isAdminUser(id: any, ctx: any, adminEmail?: string): Promise<boolean> {
9
+ if (!adminEmail || id == null) return false;
10
+ try {
11
+ const user = (await ctx.lobb.collectionStore.findOne({
12
+ collectionName: "auth_users",
13
+ id: String(id),
14
+ params: { fields: "id,email" },
15
+ })).data;
16
+ return user?.email === adminEmail;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ export function getBaseWorkflows(extensionConfig: ExtensionConfig): Workflow[] {
7
23
  return [
8
24
  {
9
25
  name: "auth_preventAdminUserDeletion",
@@ -12,7 +28,7 @@ export function getBaseWorkflows(_extensionConfig: ExtensionConfig): Workflow[]
12
28
  if (
13
29
  input.collectionName === "auth_users" &&
14
30
  input.triggeredBy === "API" &&
15
- Number(input.id) === 1
31
+ await isAdminUser(input.id, ctx, extensionConfig.admin?.email)
16
32
  ) {
17
33
  throw new ctx.LobbError({
18
34
  code: "FORBIDDEN",
@@ -28,7 +44,7 @@ export function getBaseWorkflows(_extensionConfig: ExtensionConfig): Workflow[]
28
44
  if (
29
45
  input.collectionName === "auth_users" &&
30
46
  input.triggeredBy === "API" &&
31
- Number(input.id) === 1
47
+ await isAdminUser(input.id, ctx, extensionConfig.admin?.email)
32
48
  ) {
33
49
  // Only the security-sensitive core fields are locked on the admin —
34
50
  // changing them could lock out or hijack the account. Other fields
@@ -0,0 +1,37 @@
1
+ import type {
2
+ CollectionPermissionActionsKeys,
3
+ PermissionsConfig,
4
+ } from "../config/extensionConfigSchema.ts";
5
+
6
+ // Pure permission check used by the backend's `handlePolicy` (see utils.ts) to
7
+ // gate collection actions. Walks the permissions tree and returns a yes/no;
8
+ // handlePolicy then layers on the actual guards/filters/error-throwing.
9
+ //
10
+ // It used to live under studio/shared/ because the studio's `auth.canAccess`
11
+ // workflow imported it too — now the studio inlines its own UI-gating check, so
12
+ // this is backend-only.
13
+ //
14
+ // An object value for an action means "conditional access" (filter / fields
15
+ // restrictions), which counts as allowed here; the real restriction is enforced
16
+ // per-request by handlePolicy.
17
+ export function isActionAllowed(
18
+ collection: string,
19
+ action: CollectionPermissionActionsKeys,
20
+ permissions: PermissionsConfig | undefined,
21
+ ): boolean {
22
+ if (permissions === true) return true;
23
+ if (!permissions) return false;
24
+ const collPerm = permissions[collection];
25
+ if (collPerm === true) return true;
26
+ if (!collPerm) return false;
27
+ const actionPerm = collPerm[action];
28
+ if (actionPerm === true) return true;
29
+ // A conditional grant must actually list at least one constraint
30
+ // (filter / fields / payloadGuard / mutate). Empty `{}` collapses back
31
+ // to "no grant" — explicit `true` is required for unconditional access.
32
+ return (
33
+ typeof actionPerm === "object" &&
34
+ actionPerm !== null &&
35
+ Object.keys(actionPerm).length > 0
36
+ );
37
+ }
@@ -6,7 +6,7 @@ import type {
6
6
  PermissionsConfig,
7
7
  User,
8
8
  } from "../config/extensionConfigSchema.ts";
9
- import { isActionAllowed } from "../studio/shared/permissions.ts";
9
+ import { isActionAllowed } from "./permissions.ts";
10
10
 
11
11
  interface HandlePolicyOutput {
12
12
  filter?: Filter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobb-js/lobb-ext-auth",
3
- "version": "0.15.1",
3
+ "version": "0.16.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -20,6 +20,7 @@
20
20
  "scripts": {
21
21
  "test": "bun test tests",
22
22
  "test:lobb": "bun test tests",
23
+ "test:studio": "playwright test --config studio-e2e/playwright.config.ts",
23
24
  "dev": "bun run --watch lobb.ts",
24
25
  "dev:studio": "vite dev",
25
26
  "build": "vite build",
@@ -32,13 +33,13 @@
32
33
  "package": "svelte-package --input extensions/auth/studio"
33
34
  },
34
35
  "dependencies": {
35
- "@lobb-js/core": "^0.44.0",
36
+ "@lobb-js/core": "^0.50.2",
36
37
  "argon2": "^0.40.3",
37
38
  "hono": "^4.7.0"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@sveltejs/vite-plugin-svelte": "^7.1.2",
41
- "@lobb-js/studio": "^0.53.1",
42
+ "@lobb-js/studio": "^0.60.2",
42
43
  "@lucide/svelte": "^0.563.1",
43
44
  "@sveltejs/adapter-node": "^5.5.4",
44
45
  "@sveltejs/kit": "^2.60.1",
@@ -1,2 +0,0 @@
1
- import type { CollectionPermissionActionsKeys, PermissionsConfig } from "../../config/extensionConfigSchema.ts";
2
- export declare function isActionAllowed(collection: string, action: CollectionPermissionActionsKeys, permissions: PermissionsConfig | undefined): boolean;
@@ -1,35 +0,0 @@
1
- // Lives under studio/ for packaging reasons — `svelte-package` only bundles
2
- // files inside its `--input` root, so anything the studio entry imports has
3
- // to be reachable from there. The backend imports this same file via
4
- // `../studio/shared/permissions` so there's still one source of truth.
5
- //
6
- // Anything in this folder must stay environment-neutral (no DOM, no Node-only
7
- // APIs, no top-level side effects) so it runs the same way on both sides.
8
- // Pure permission check shared by the server-side handlePolicy and the
9
- // studio's auth.canAccess workflow. Walks the permissions tree and returns
10
- // a yes/no — server-side then layers on guards/filters/error-throwing,
11
- // client-side uses the answer to decide whether to render UI.
12
- //
13
- // An object value for action permission means "conditional access" (filter
14
- // and/or fields restrictions). For UI gating that counts as allowed; the
15
- // server enforces the actual restrictions on real requests.
16
- export function isActionAllowed(collection, action, permissions) {
17
- if (permissions === true)
18
- return true;
19
- if (!permissions)
20
- return false;
21
- const collPerm = permissions[collection];
22
- if (collPerm === true)
23
- return true;
24
- if (!collPerm)
25
- return false;
26
- const actionPerm = collPerm[action];
27
- if (actionPerm === true)
28
- return true;
29
- // A conditional grant must actually list at least one constraint
30
- // (filter / fields / payloadGuard / mutate). Empty `{}` collapses back
31
- // to "no grant" — explicit `true` is required for unconditional access.
32
- return (typeof actionPerm === "object" &&
33
- actionPerm !== null &&
34
- Object.keys(actionPerm).length > 0);
35
- }
@@ -1,42 +0,0 @@
1
- // Lives under studio/ for packaging reasons — `svelte-package` only bundles
2
- // files inside its `--input` root, so anything the studio entry imports has
3
- // to be reachable from there. The backend imports this same file via
4
- // `../studio/shared/permissions` so there's still one source of truth.
5
- //
6
- // Anything in this folder must stay environment-neutral (no DOM, no Node-only
7
- // APIs, no top-level side effects) so it runs the same way on both sides.
8
-
9
- import type {
10
- CollectionPermissionActionsKeys,
11
- PermissionsConfig,
12
- } from "../../config/extensionConfigSchema.ts";
13
-
14
- // Pure permission check shared by the server-side handlePolicy and the
15
- // studio's auth.canAccess workflow. Walks the permissions tree and returns
16
- // a yes/no — server-side then layers on guards/filters/error-throwing,
17
- // client-side uses the answer to decide whether to render UI.
18
- //
19
- // An object value for action permission means "conditional access" (filter
20
- // and/or fields restrictions). For UI gating that counts as allowed; the
21
- // server enforces the actual restrictions on real requests.
22
- export function isActionAllowed(
23
- collection: string,
24
- action: CollectionPermissionActionsKeys,
25
- permissions: PermissionsConfig | undefined,
26
- ): boolean {
27
- if (permissions === true) return true;
28
- if (!permissions) return false;
29
- const collPerm = permissions[collection];
30
- if (collPerm === true) return true;
31
- if (!collPerm) return false;
32
- const actionPerm = collPerm[action];
33
- if (actionPerm === true) return true;
34
- // A conditional grant must actually list at least one constraint
35
- // (filter / fields / payloadGuard / mutate). Empty `{}` collapses back
36
- // to "no grant" — explicit `true` is required for unconditional access.
37
- return (
38
- typeof actionPerm === "object" &&
39
- actionPerm !== null &&
40
- Object.keys(actionPerm).length > 0
41
- );
42
- }