najm-auth 4.0.9 → 4.2.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/README.md CHANGED
@@ -508,44 +508,36 @@ export const Product = own(products)
508
508
  join(products.userId, _users.id),
509
509
  where(_users.id)
510
510
  )
511
- .writeBy(products.userId); // Enforce on create/update
511
+ .writeBy(products.userId); // Metadata for consumers; not an automatic write guard
512
512
  ```
513
513
 
514
514
  ### Using @Policy and @Owned
515
515
 
516
516
  ```typescript
517
- import { configureOwnership, Policy, CanList, CanRead, CanCreate, CanUpdate, CanDelete } from 'najm-auth';
518
-
519
- const config = configureOwnership({
520
- adminRoles: ['admin'],
521
- rules: {
522
- 'user': {
523
- 'products': Product.getRules()['user']
524
- }
525
- }
526
- });
517
+ import { Owned, Policy, CanList, CanRead, CanCreate, CanUpdate, CanDelete } from 'najm-auth';
518
+ import { and, eq, type SQL } from 'drizzle-orm';
527
519
 
528
520
  @Policy(Product)
529
521
  @Controller('/api/products')
530
522
  export class ProductController {
531
523
  @Get('/')
532
- @CanList() // List only owned products
533
- getAll(@GuardParams() filter: any) { }
524
+ @CanList() // Checks read permission; repository scopes rows
525
+ getAll() { /* delegate to a scoped service/repository read */ }
534
526
 
535
527
  @Get('/:id')
536
- @CanRead() // Read only if owner
528
+ @CanRead() // Checks read permission; lookup must scope the ID
537
529
  getOne() { }
538
530
 
539
531
  @Post('/')
540
- @CanCreate() // Create (ownership assigned automatically)
532
+ @CanCreate() // Service must validate/assign ownership
541
533
  create(@Body() data: any) { }
542
534
 
543
535
  @Put('/:id')
544
- @CanUpdate() // Update only if owner
536
+ @CanUpdate() // Service must load an owned record before writing
545
537
  update(@Body() data: any) { }
546
538
 
547
539
  @Delete('/:id')
548
- @CanDelete() // Delete only if owner
540
+ @CanDelete() // Service must load an owned record before deleting
549
541
  delete() { }
550
542
  }
551
543
 
@@ -554,21 +546,84 @@ export class ProductController {
554
546
  export class ProductRepository {
555
547
  @DB() db!: Database;
556
548
 
557
- // Auto-scoped to current user
558
- async findMany(opts?: { where?: any; limit?: number }) {
559
- return this.findMany(opts); // Only returns owned products
560
- }
549
+ // These methods are supplied by @Owned; do not define recursive wrappers.
550
+ declare ownershipCondition: () => SQL | undefined;
551
+ declare findMany: (opts?: { where?: SQL; limit?: number }) => Promise<ProductRow[]>;
552
+ declare findOne: (opts: { where: SQL }) => Promise<ProductRow | null>;
561
553
 
562
- async findOne(opts: { where: any }) {
563
- return this.findOne(opts); // Returns null if not owned
554
+ async getById(id: string) {
555
+ const [row] = await this.db.select().from(products)
556
+ .where(and(this.ownershipCondition(), eq(products.id, id))).limit(1);
557
+ return row ?? null;
564
558
  }
559
+ }
560
+ ```
561
+
562
+ ### Composable ownership (4.1+)
563
+
564
+ For custom queries, the property form puts the rules and type together:
565
+
566
+ ```typescript
567
+ import { Owned, type OwnedWhere } from 'najm-auth';
565
568
 
566
- async scopedQuery() {
567
- return this.scopedQuery(); // Raw scoped query builder
569
+ @Repository('default')
570
+ export class ProductRepository {
571
+ @DB() db!: Database;
572
+
573
+ @Owned(Product, SharedProduct)
574
+ private ownedWhere!: OwnedWhere;
575
+
576
+ async getById(id: string) {
577
+ const [row] = await this.db.select().from(products)
578
+ .where(and(this.ownedWhere(), eq(products.id, id))).limit(1);
579
+ return row ?? null;
568
580
  }
569
581
  }
570
582
  ```
571
583
 
584
+ Use any instance property name; `OwnedWhere` is the shared function type.
585
+ Resolve the repository through Najm's container before calling it. Context
586
+ injection installs the function after construction, including when TypeScript
587
+ emits native class fields. The function reads the current request on every call,
588
+ so a singleton repository can safely serve different actors. Multiple decorated
589
+ properties may use independent rules, including on inherited repositories.
590
+ The class form and its `ownershipCondition`, `findMany`, `findOne`, and
591
+ `scopedQuery` helpers remain available. The property form supplies only the
592
+ decorated predicate; it does not rewrite queries or add CRUD helpers.
593
+
594
+ `@Owned(Product)` now provides `ownershipCondition()` for custom queries.
595
+ It returns a parameterized `id IN (authorized IDs)` predicate; join-based rules
596
+ do not multiply outer rows. Combine it with filters in one
597
+ `.where(and(this.ownershipCondition(), filter))`. The owned table must have an
598
+ `id` column. A standalone `ownershipCondition(db, tokens, context)` and the
599
+ `OwnershipReadContext` / `OwnershipConditionMethods` types are also exported.
600
+
601
+ `@Owned(Product, SharedProduct)` grants either rule (OR), then your query's
602
+ filters apply (AND). Every alternative must reference the same table object.
603
+ Alternatives work with `ownershipCondition`, `findMany`, `findOne`, and
604
+ `scopedQuery`. Single-token helpers retain their existing behavior.
605
+
606
+ Configure school-wide, tenant-wide, or other privileged roles in the app:
607
+
608
+ ```typescript
609
+ const Product = own(products, { adminRoles: ['admin', 'operations'] })
610
+ .for('member', where(products.userId));
611
+ ```
612
+
613
+ Those roles bypass row ownership only; route permissions still apply. Unknown
614
+ roles and anonymous active requests get no rows. Outside request context,
615
+ seed/job reads remain unscoped for compatibility; never treat a manually
616
+ constructed repository without request injection as an authenticated boundary.
617
+
618
+ **Migrating custom queries:** replace `this.scope(query).where(filter)` with
619
+ `query.where(and(this.ownershipCondition(), filter))`. Drizzle replaces an
620
+ earlier WHERE when another `.where()` is called. The legacy `scope()` and
621
+ `scopedQuery()` APIs remain for compatibility, but their result must not be
622
+ followed by `.where()`; for simple reads use `findMany({ where: filter })`.
623
+ An upgrade alone does not rewrite unsafe queries in consumers. Keep role
624
+ and cross-user integration tests in each app. `@Policy` and `@CanRead` check
625
+ permissions, not the ownership of a requested record ID.
626
+
572
627
  ### Advanced Ownership: Multi-Role Scoping
573
628
 
574
629
  ```typescript
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { CredentialSetupRequirement, CredentialSetupSession, NewCredentialSetupR
14
14
  import { CookieService } from 'najm-cookies';
15
15
  import { Context } from 'hono';
16
16
  import { GuardResult } from 'najm-guard';
17
- import 'drizzle-orm';
17
+ import { SQL } from 'drizzle-orm';
18
18
  import 'drizzle-orm/pg-core';
19
19
 
20
20
  interface CredentialSetupOptions {
@@ -2856,7 +2856,32 @@ declare class ScopeContext {
2856
2856
  role: string;
2857
2857
  } | null;
2858
2858
  }
2859
- declare function Owned(token: OwnershipToken): (target: Function) => void;
2859
+ /** Decorate a repository class, or an `ownedWhere!: OwnedWhere` property. */
2860
+ declare function Owned(token: OwnershipToken, ...alternatives: OwnershipToken[]): ClassDecorator & PropertyDecorator;
2861
+
2862
+ /** Request-aware ownership predicate supplied by an @Owned property. */
2863
+ type OwnedWhere = () => SQL | undefined;
2864
+ /** Request context consumed by ownershipCondition; ScopeContext implements it. */
2865
+ interface OwnershipReadContext {
2866
+ hasActiveContext(): boolean;
2867
+ getUser(): {
2868
+ id: string;
2869
+ role: string;
2870
+ } | null;
2871
+ }
2872
+ /** Additional custom-query method installed by @Owned. */
2873
+ interface OwnershipConditionMethods {
2874
+ ownershipCondition(): SQL | undefined;
2875
+ }
2876
+ /**
2877
+ * Compose this predicate with application filters in ONE .where(and(...)).
2878
+ * Each token grants alternative access to the same table through an ID
2879
+ * subquery, so joins cannot duplicate outer rows or collide with outer joins.
2880
+ * The table must expose an `id` column. Roles and ownership rules remain
2881
+ * application-owned. Unknown roles and anonymous active requests deny all.
2882
+ * Outside a request, reads remain unscoped for seed/job compatibility.
2883
+ */
2884
+ declare function ownershipCondition(db: any, tokens: readonly OwnershipToken[], context: OwnershipReadContext | undefined): SQL | undefined;
2860
2885
 
2861
2886
  /**
2862
2887
  * Shared query builders used across auth repositories.
@@ -3181,4 +3206,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
3181
3206
  */
3182
3207
  declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
3183
3208
 
3184
- export { AUTH_CONFIG, AUTH_CORE_MODULE, en as AUTH_EN, AUTH_LOCALES, AUTH_LOGIN_RATE_LIMIT_ENV, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AdministrativeDelivery, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthLoginRateLimitConfig, type AuthPluginConfig, AuthQueries, type AuthRateLimitEnvironment, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, CREDENTIAL_SETUP_CODES, CREDENTIAL_SETUP_MODULE, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, type ConsumedSetPasswordToken, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type CredentialSetupChangeDto, type CredentialSetupCode, type CredentialSetupConfig, CredentialSetupController, type CredentialSetupOptions, type CredentialSetupPasswordOptions, type CredentialSetupPending, CredentialSetupRepository, CredentialSetupRequirementRepository, type CredentialSetupRequirementRow, CredentialSetupRequirementService, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, DEFAULT_AUTH_LOGIN_RATE_LIMIT, DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME, DEFAULT_CREDENTIAL_SETUP_TTL_MS, type DefineRolesOptions, type EmailParam, EncryptionService, type GitHubOAuthConfig, type GoogleOAuthConfig, IdentityConfig, type IdentityResolver, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, type LoginResult, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, PASSWORD_SETUP_PURPOSE, PUBLIC_REGISTRATION_MODULE, PasswordSetupService, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, type ProvisionUserWithPasswordInput, type ProvisionUserWithSetupInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, RegistrationController, type ResetPasswordDto, type ResolvedCredentialSetupConfig, ResolvedIdentityConfig, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type RunAsUser, type SanitizedUser, ScopeContext, type ScopeResult, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, type SessionCookieData, SessionInvalidationService, type SetPasswordToken, type SetPasswordTokenType, TOKEN_STATUS, TOKEN_TYPE, TemporaryCredentialInput, type TemporaryCredentialReset, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, type UserListQuery, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authEmailRateLimitKey, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createIdentityResolver, createPermissionDto, createRoleDto, createTokenDto, createUserDto, credentialSetupChangeDto, credentialSetupError, defaultCredentialSetupPasswordSchema, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, normalizeSetupPurpose, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, resolveAuthLoginRateLimitConfig, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
3209
+ export { AUTH_CONFIG, AUTH_CORE_MODULE, en as AUTH_EN, AUTH_LOCALES, AUTH_LOGIN_RATE_LIMIT_ENV, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AdministrativeDelivery, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthLoginRateLimitConfig, type AuthPluginConfig, AuthQueries, type AuthRateLimitEnvironment, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, CREDENTIAL_SETUP_CODES, CREDENTIAL_SETUP_MODULE, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, type ConsumedSetPasswordToken, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type CredentialSetupChangeDto, type CredentialSetupCode, type CredentialSetupConfig, CredentialSetupController, type CredentialSetupOptions, type CredentialSetupPasswordOptions, type CredentialSetupPending, CredentialSetupRepository, CredentialSetupRequirementRepository, type CredentialSetupRequirementRow, CredentialSetupRequirementService, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, DEFAULT_AUTH_LOGIN_RATE_LIMIT, DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME, DEFAULT_CREDENTIAL_SETUP_TTL_MS, type DefineRolesOptions, type EmailParam, EncryptionService, type GitHubOAuthConfig, type GoogleOAuthConfig, IdentityConfig, type IdentityResolver, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, type LoginResult, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnedWhere, type OwnershipConditionMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipReadContext, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, PASSWORD_SETUP_PURPOSE, PUBLIC_REGISTRATION_MODULE, PasswordSetupService, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, type ProvisionUserWithPasswordInput, type ProvisionUserWithSetupInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, RegistrationController, type ResetPasswordDto, type ResolvedCredentialSetupConfig, ResolvedIdentityConfig, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type RunAsUser, type SanitizedUser, ScopeContext, type ScopeResult, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, type SessionCookieData, SessionInvalidationService, type SetPasswordToken, type SetPasswordTokenType, TOKEN_STATUS, TOKEN_TYPE, TemporaryCredentialInput, type TemporaryCredentialReset, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, type UserListQuery, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authEmailRateLimitKey, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createIdentityResolver, createPermissionDto, createRoleDto, createTokenDto, createUserDto, credentialSetupChangeDto, credentialSetupError, defaultCredentialSetupPasswordSchema, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, normalizeSetupPurpose, own, ownershipCondition, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, resolveAuthLoginRateLimitConfig, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
package/dist/index.js CHANGED
@@ -5971,7 +5971,7 @@ var OwnershipToken = class {
5971
5971
  symbol;
5972
5972
  name;
5973
5973
  table;
5974
- _rules = {};
5974
+ _rules = /* @__PURE__ */ Object.create(null);
5975
5975
  _writeScopeCol;
5976
5976
  _adminRoles;
5977
5977
  constructor(table, opts) {
@@ -6383,9 +6383,47 @@ __name(Policy, "Policy");
6383
6383
 
6384
6384
  // src/ownership/OwnedDecorator.ts
6385
6385
  import "reflect-metadata";
6386
- import { sql as sql5, and as and5 } from "drizzle-orm";
6386
+ import { sql as sql6, and as and5 } from "drizzle-orm";
6387
6387
  import { Injectable as Injectable17, Inject as Inject17, DI as DI3, Container as Container2, REQUEST_ID } from "najm-core";
6388
6388
  import { USER as USER2 } from "najm-guard";
6389
+
6390
+ // src/ownership/ownershipCondition.ts
6391
+ import { inArray, or, sql as sql5 } from "drizzle-orm";
6392
+ function validateOwnershipAlternatives(tokens) {
6393
+ if (!tokens.length)
6394
+ throw new Error("Ownership requires at least one token");
6395
+ const table = tokens[0].table;
6396
+ if (tokens.some((token) => token.table !== table)) {
6397
+ throw new Error("Ownership alternatives must refer to the same table");
6398
+ }
6399
+ }
6400
+ __name(validateOwnershipAlternatives, "validateOwnershipAlternatives");
6401
+ function ownershipCondition(db, tokens, context) {
6402
+ validateOwnershipAlternatives(tokens);
6403
+ const idColumn = tokens[0].table.id;
6404
+ if (!idColumn)
6405
+ throw new Error("ownershipCondition requires a table with an id column");
6406
+ if (!context?.hasActiveContext())
6407
+ return void 0;
6408
+ const user = context.getUser();
6409
+ if (!user)
6410
+ return sql5`1 = 0`;
6411
+ const conditions = [];
6412
+ for (const token of tokens) {
6413
+ const { query, condition } = token.applyScopeSplit(user.id, user.role, db.select({ id: idColumn }).from(token.table));
6414
+ if (condition === null)
6415
+ return void 0;
6416
+ if (!Object.prototype.hasOwnProperty.call(token.getRules(), user.role))
6417
+ continue;
6418
+ conditions.push(inArray(idColumn, query.where(condition)));
6419
+ }
6420
+ if (!conditions.length)
6421
+ return sql5`1 = 0`;
6422
+ return conditions.length === 1 ? conditions[0] : or(...conditions);
6423
+ }
6424
+ __name(ownershipCondition, "ownershipCondition");
6425
+
6426
+ // src/ownership/OwnedDecorator.ts
6389
6427
  var __decorate33 = function(decorators, target, key, desc) {
6390
6428
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6391
6429
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -6397,6 +6435,43 @@ var __metadata33 = function(k, v) {
6397
6435
  };
6398
6436
  var _a21;
6399
6437
  var OWNED_META = Symbol.for("najm:owned");
6438
+ var OWNED_PROPERTIES = Symbol.for("najm:owned-properties");
6439
+ var propertyContexts = /* @__PURE__ */ new WeakMap();
6440
+ function ownedProperty(proto, key, tokens) {
6441
+ const inherited = Reflect.getMetadata(OWNED_PROPERTIES, proto) ?? [];
6442
+ const properties = [...inherited.filter((property) => property.key !== key), { key, tokens }];
6443
+ Reflect.defineMetadata(OWNED_PROPERTIES, properties, proto);
6444
+ Object.defineProperty(proto, key, {
6445
+ value() {
6446
+ return ownershipCondition(this.db, tokens, this._scopeCtx);
6447
+ },
6448
+ configurable: true,
6449
+ enumerable: false
6450
+ });
6451
+ if (!Reflect.hasOwnMetadata(OWNED_PROPERTIES, proto, "_scopeCtx")) {
6452
+ Inject17(ScopeContext)(proto, "_scopeCtx");
6453
+ Reflect.defineMetadata(OWNED_PROPERTIES, true, proto, "_scopeCtx");
6454
+ Object.defineProperty(proto, "_scopeCtx", {
6455
+ get() {
6456
+ return propertyContexts.get(this);
6457
+ },
6458
+ set(context) {
6459
+ propertyContexts.set(this, context);
6460
+ const properties2 = Reflect.getMetadata(OWNED_PROPERTIES, this) ?? [];
6461
+ for (const property of properties2) {
6462
+ Object.defineProperty(this, property.key, {
6463
+ value: /* @__PURE__ */ __name(() => ownershipCondition(this.db, property.tokens, this._scopeCtx), "value"),
6464
+ configurable: true,
6465
+ enumerable: false
6466
+ });
6467
+ }
6468
+ },
6469
+ configurable: true,
6470
+ enumerable: false
6471
+ });
6472
+ }
6473
+ }
6474
+ __name(ownedProperty, "ownedProperty");
6400
6475
  var ScopeContext = class ScopeContext2 {
6401
6476
  static {
6402
6477
  __name(this, "ScopeContext");
@@ -6434,11 +6509,28 @@ __decorate33([
6434
6509
  ScopeContext = __decorate33([
6435
6510
  Injectable17()
6436
6511
  ], ScopeContext);
6437
- function Owned(token) {
6438
- return function(target) {
6512
+ function Owned(token, ...alternatives) {
6513
+ const tokens = [token, ...alternatives];
6514
+ validateOwnershipAlternatives(tokens);
6515
+ return function(target, propertyKey) {
6516
+ if (propertyKey !== void 0) {
6517
+ if (typeof target === "function")
6518
+ throw new Error("@Owned requires an instance property");
6519
+ ownedProperty(target, propertyKey, tokens);
6520
+ return;
6521
+ }
6522
+ if (typeof target !== "function")
6523
+ throw new Error("@Owned requires a repository class");
6439
6524
  Reflect.defineMetadata(OWNED_META, token, target);
6440
6525
  const proto = target.prototype;
6441
6526
  Inject17(ScopeContext)(proto, "_scopeCtx");
6527
+ Object.defineProperty(proto, "ownershipCondition", {
6528
+ value() {
6529
+ return ownershipCondition(this.db, tokens, this._scopeCtx);
6530
+ },
6531
+ configurable: true,
6532
+ enumerable: false
6533
+ });
6442
6534
  function getUser(self) {
6443
6535
  return self._scopeCtx?.getUser() ?? null;
6444
6536
  }
@@ -6470,9 +6562,11 @@ function Owned(token) {
6470
6562
  return (query) => {
6471
6563
  if (!hasActiveContext(this))
6472
6564
  return query;
6565
+ if (alternatives.length)
6566
+ return query.where(this.ownershipCondition());
6473
6567
  const user = getUser(this);
6474
6568
  if (!user)
6475
- return query.where(sql5`1 = 0`);
6569
+ return query.where(sql6`1 = 0`);
6476
6570
  return token.applyScope(user.id, user.role, query);
6477
6571
  };
6478
6572
  },
@@ -6489,6 +6583,9 @@ function Owned(token) {
6489
6583
  if (!user)
6490
6584
  return [];
6491
6585
  const base = this.db.select().from(token.table);
6586
+ if (alternatives.length) {
6587
+ return applyQueryOptions(base, opts, combineConditions(this.ownershipCondition(), opts.where));
6588
+ }
6492
6589
  const { query: q, condition: scopeCondition } = token.applyScopeSplit(user.id, user.role, base);
6493
6590
  return applyQueryOptions(q, opts, combineConditions(scopeCondition, opts.where));
6494
6591
  };
@@ -8113,6 +8210,7 @@ export {
8113
8210
  normalizeTunisianPhone,
8114
8211
  oauthAccountsTable,
8115
8212
  own,
8213
+ ownershipCondition,
8116
8214
  parseSchema,
8117
8215
  permissionIdParam,
8118
8216
  permissionsTable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "4.0.9",
3
+ "version": "4.2.0",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [
@@ -92,7 +92,7 @@
92
92
  "dependencies": {
93
93
  "bcryptjs": "^3.0.2",
94
94
  "najm-cookies": "^2.0.2",
95
- "najm-core": "^2.0.6",
95
+ "najm-core": "^2.1.0",
96
96
  "najm-database": "^2.0.4",
97
97
  "najm-guard": "^2.0.2",
98
98
  "najm-i18n": "^2.1.2",