najm-auth 1.1.3 → 1.1.4

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.d.ts CHANGED
@@ -1294,97 +1294,126 @@ interface ResourceGuards {
1294
1294
  */
1295
1295
  declare function createResourceGuards<T extends new (...args: any[]) => OwnershipProvider>(ownershipClass: T, resourceType: string, resource: string, options?: ResourceGuardsOptions): ResourceGuards;
1296
1296
 
1297
- /**
1298
- * A resolver function that returns accessible IDs for a given userId.
1299
- */
1300
1297
  type OwnershipResolver = (userId: string) => Promise<string[]>;
1301
- /**
1302
- * Rules map: role → resource → resolver function.
1303
- *
1304
- * @example
1305
- * ```ts
1306
- * {
1307
- * teacher: {
1308
- * student: (uid) => db.select(...).from(teachers).where(eq(teachers.userId, uid)).then(r => r.map(x => x.id)),
1309
- * class: (uid) => db.select(...).from(classes).where(...).then(r => r.map(x => x.id)),
1310
- * },
1311
- * parent: {
1312
- * student: (uid) => ...,
1313
- * },
1314
- * }
1315
- * ```
1316
- */
1317
1298
  type OwnershipRules = Record<string, Record<string, OwnershipResolver>>;
1318
1299
  interface OwnershipConfig {
1319
1300
  /** Roles that bypass ownership and can access everything */
1320
1301
  adminRoles: string[];
1302
+ /**
1303
+ * Default write guard applied to create/delete across all resources.
1304
+ * Can be overridden per-resource in `guards()` or `for()`.
1305
+ * Defaults to `isAdmin` if not provided.
1306
+ *
1307
+ * @example
1308
+ * adminGuard: isAdministrator // set once, applies everywhere
1309
+ */
1310
+ adminGuard?: () => ClassDecorator & MethodDecorator;
1321
1311
  /** Resolution rules: role → resource → resolver */
1322
1312
  rules: OwnershipRules;
1323
1313
  /** Cache TTL in ms (default: 60000) */
1324
1314
  cacheTtl?: number;
1325
1315
  }
1316
+ /**
1317
+ * A guard factory that is also chainable.
1318
+ * Call it as `@canCreateExam` (decorator) or chain `.body()` / `.and()` before use.
1319
+ *
1320
+ * @example
1321
+ * ```ts
1322
+ * const g = ownership.for('exams');
1323
+ *
1324
+ * export const canAccessExam = g.read;
1325
+ * export const canCreateExam = g.create.body('section', 'sectionId');
1326
+ * export const canCreateEvent = g.create
1327
+ * .body('class', 'classId', true)
1328
+ * .body('section', 'sectionId', true);
1329
+ * ```
1330
+ */
1331
+ type ChainableGuard = {
1332
+ (): ClassDecorator & MethodDecorator;
1333
+ /**
1334
+ * Add an ownership check on a body field.
1335
+ * @param type - singular resource type (e.g. 'section')
1336
+ * @param field - body field name (e.g. 'sectionId')
1337
+ * @param optional - if true, missing field passes (default: false)
1338
+ */
1339
+ body(type: string, field: string, optional?: boolean): ChainableGuard;
1340
+ /**
1341
+ * Add any custom guard decorator into the chain.
1342
+ */
1343
+ and(guard: () => ClassDecorator & MethodDecorator): ChainableGuard;
1344
+ };
1345
+ /**
1346
+ * Fluent resource accessor returned by `ownership.for(resource)`.
1347
+ * Each property is a `ChainableGuard` for that CRUD action.
1348
+ */
1349
+ interface ResourceAccessor {
1350
+ /** Read single resource — checks :id param ownership */
1351
+ readonly read: ChainableGuard;
1352
+ /** Update single resource — checks :id param ownership */
1353
+ readonly update: ChainableGuard;
1354
+ /** Create resource — admin-only by default, chain `.body()` for extra checks */
1355
+ readonly create: ChainableGuard;
1356
+ /** Delete resource — admin-only by default */
1357
+ readonly delete: ChainableGuard;
1358
+ /** List resources — sets @Filter() ALS token for scoped queries */
1359
+ readonly list: ChainableGuard;
1360
+ }
1326
1361
  interface ConfiguredOwnership {
1327
1362
  /** The generated OwnershipService class — register this with your DI container */
1328
1363
  Service: new (...args: any[]) => OwnershipProvider & {
1329
1364
  clearCache(userId?: string): void;
1330
1365
  };
1331
1366
  /**
1332
- * Generate CRUD guards for a resource.
1367
+ * Fluent resource accessor. Each property is a `ChainableGuard`.
1368
+ * Chain `.body(type, field)` on `create`/`update` for additional body checks.
1333
1369
  *
1334
- * @param resource - plural resource name (e.g. 'teachers')
1335
- * The singular form is auto-inferred for ownership lookups.
1370
+ * @example
1371
+ * ```ts
1372
+ * const g = ownership.for('exams');
1373
+ *
1374
+ * export const canAccessExam = g.read;
1375
+ * export const canUpdateExam = g.update;
1376
+ * export const canCreateExam = g.create.body('section', 'sectionId');
1377
+ * export const canDeleteExam = g.delete;
1378
+ * export const canAccessAllExams = g.list;
1379
+ * ```
1380
+ */
1381
+ for(resource: string, options?: ResourceGuardsOptions): ResourceAccessor;
1382
+ /**
1383
+ * Generate CRUD guards for a resource (destructuring style).
1384
+ * Prefer `for()` for new code — this is kept for backward compatibility.
1336
1385
  *
1337
1386
  * @example
1338
1387
  * ```ts
1339
1388
  * export const {
1340
1389
  * canRead: canAccessTeacher,
1341
- * canUpdate: canUpdateTeacher,
1342
- * canCreate: canCreateTeacher,
1343
- * canDelete: canDeleteTeacher,
1344
1390
  * canList: canAccessAllTeachers,
1345
1391
  * } = ownership.guards('teachers');
1346
1392
  * ```
1347
1393
  */
1348
1394
  guards(resource: string, options?: ResourceGuardsOptions): ResourceGuards;
1395
+ /**
1396
+ * Create a standalone body-field ownership guard.
1397
+ * Prefer chaining `.body()` on `ownership.for()` — this is kept for manual composition.
1398
+ */
1399
+ bodyGuard(resourceType: string, bodyField: string, optional?: boolean): () => ClassDecorator & MethodDecorator;
1349
1400
  }
1350
1401
  /**
1351
1402
  * Configure ownership for your entire app in one place.
1352
1403
  *
1353
- * Returns a `Service` class (for DI) and a `guards()` helper
1354
- * that replaces per-module boilerplate.
1355
- *
1356
1404
  * @example
1357
1405
  * ```ts
1358
1406
  * // ownership.ts
1359
- * import { configureOwnership } from 'najm-auth';
1360
- * import { db } from './database/db';
1361
- * import { eq } from 'drizzle-orm';
1362
- * import { students, teachers, parents } from './database/schema';
1363
- *
1364
1407
  * export const ownership = configureOwnership({
1365
1408
  * adminRoles: ['admin', 'principal'],
1366
- * rules: {
1367
- * student: {
1368
- * student: async (uid) => {
1369
- * const rows = await db.select({ id: students.id }).from(students).where(eq(students.userId, uid));
1370
- * return rows.map(r => r.id);
1371
- * },
1372
- * },
1373
- * teacher: {
1374
- * student: async (uid) => {
1375
- * const rows = await db.select({ id: students.id })
1376
- * .from(teachers)
1377
- * .innerJoin(teacherAssignments, eq(teachers.id, teacherAssignments.teacherId))
1378
- * .innerJoin(students, eq(teacherAssignments.sectionId, students.sectionId))
1379
- * .where(eq(teachers.userId, uid));
1380
- * return rows.map(r => r.id);
1381
- * },
1382
- * },
1383
- * },
1409
+ * adminGuard: isAdministrator, // ← set once, no need to repeat
1410
+ * rules: { teacher: { student: ... }, parent: { student: ... } },
1384
1411
  * });
1385
1412
  *
1386
- * // TeacherGuards.ts — one line
1387
- * export const { canRead: canAccessTeacher, ... } = ownership.guards('teachers');
1413
+ * // ExamGuards.ts
1414
+ * const g = ownership.for('exams');
1415
+ * export const canAccessExam = g.read;
1416
+ * export const canCreateExam = g.create.body('section', 'sectionId');
1388
1417
  * ```
1389
1418
  */
1390
1419
  declare function configureOwnership(config: OwnershipConfig): ConfiguredOwnership;
@@ -1633,4 +1662,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
1633
1662
  */
1634
1663
  declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
1635
1664
 
1636
- export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthPluginConfig, AuthQueries, type AuthSchema, type AuthSeedConfig, AuthService, type AuthUser, Can, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type EmailParam, EncryptionService, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, NewPermission, NewRoleEntity, NewUser, type OwnershipConfig, type OwnershipProvider, type OwnershipResolver, type OwnershipRules, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, ROLES, ROLE_GROUPS, type RefreshTokenDto, type ResetPasswordDto, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type SanitizedUser, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, TOKEN_STATUS, TOKEN_TYPE, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, canCreate, canDelete, canManage, canRead, canUpdate, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createPermissionDto, createResourceGuards, createRoleDto, createTokenDto, createUserDto, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, isAdmin, isAdministrator, isAuth, isEmpty, isFile, isPath, languageParam, loginDto, parseSchema, permissionIdParam, pickProps, refreshTokenDto, resetPasswordDto, revokeTokenDto, roleIdParam, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, verifyTokenDto };
1665
+ export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthPluginConfig, AuthQueries, type AuthSchema, type AuthSeedConfig, AuthService, type AuthUser, Can, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type EmailParam, EncryptionService, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, NewPermission, NewRoleEntity, NewUser, type OwnershipConfig, type OwnershipProvider, type OwnershipResolver, type OwnershipRules, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, ROLES, ROLE_GROUPS, type RefreshTokenDto, type ResetPasswordDto, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type SanitizedUser, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, TOKEN_STATUS, TOKEN_TYPE, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, canCreate, canDelete, canManage, canRead, canUpdate, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createPermissionDto, createResourceGuards, createRoleDto, createTokenDto, createUserDto, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, isAdmin, isAdministrator, isAuth, isEmpty, isFile, isPath, languageParam, loginDto, parseSchema, permissionIdParam, pickProps, refreshTokenDto, resetPasswordDto, revokeTokenDto, roleIdParam, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, verifyTokenDto };
package/dist/index.js CHANGED
@@ -3093,13 +3093,22 @@ function createResourceGuards(ownershipClass, resourceType, resource, options) {
3093
3093
  __name(createResourceGuards, "createResourceGuards");
3094
3094
 
3095
3095
  // src/ownership/configureOwnership.ts
3096
- import { Injectable as Injectable12 } from "najm-core";
3096
+ import { Injectable as Injectable12, Inject as Inject10, User as User5, Body as Body5 } from "najm-core";
3097
+ import { createGuard as createGuard6, composeGuards as composeGuards5 } from "najm-guard";
3097
3098
  var __decorate23 = function(decorators, target, key, desc) {
3098
3099
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3099
3100
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3100
3101
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
3101
3102
  return c > 3 && r && Object.defineProperty(target, key, r), r;
3102
3103
  };
3104
+ var __metadata23 = function(k, v) {
3105
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3106
+ };
3107
+ var __param10 = function(paramIndex, decorator) {
3108
+ return function(target, key) {
3109
+ decorator(target, key, paramIndex);
3110
+ };
3111
+ };
3103
3112
  function toSingular(plural) {
3104
3113
  if (plural.endsWith("ies"))
3105
3114
  return plural.slice(0, -3) + "y";
@@ -3112,7 +3121,7 @@ function toSingular(plural) {
3112
3121
  }
3113
3122
  __name(toSingular, "toSingular");
3114
3123
  function configureOwnership(config) {
3115
- const { adminRoles, rules, cacheTtl = 6e4 } = config;
3124
+ const { adminRoles, rules, cacheTtl = 6e4, adminGuard: defaultAdminGuard = isAdmin } = config;
3116
3125
  let GeneratedOwnershipService = class GeneratedOwnershipService {
3117
3126
  static {
3118
3127
  __name(this, "GeneratedOwnershipService");
@@ -3163,14 +3172,79 @@ function configureOwnership(config) {
3163
3172
  GeneratedOwnershipService = __decorate23([
3164
3173
  Injectable12()
3165
3174
  ], GeneratedOwnershipService);
3175
+ function bodyGuard(resourceType, bodyField, optional = false) {
3176
+ var _a15;
3177
+ let BodyAccessGuard = class BodyAccessGuard {
3178
+ static {
3179
+ __name(this, "BodyAccessGuard");
3180
+ }
3181
+ ownership;
3182
+ async canActivate(user, body) {
3183
+ const id = body[bodyField];
3184
+ if (!id)
3185
+ return optional;
3186
+ return this.ownership.canAccess(user, resourceType, id);
3187
+ }
3188
+ };
3189
+ __decorate23([
3190
+ Inject10(GeneratedOwnershipService),
3191
+ __metadata23("design:type", GeneratedOwnershipService)
3192
+ ], BodyAccessGuard.prototype, "ownership", void 0);
3193
+ __decorate23([
3194
+ __param10(0, User5()),
3195
+ __param10(1, Body5()),
3196
+ __metadata23("design:type", Function),
3197
+ __metadata23("design:paramtypes", [Object, Object]),
3198
+ __metadata23("design:returntype", typeof (_a15 = typeof Promise !== "undefined" && Promise) === "function" ? _a15 : Object)
3199
+ ], BodyAccessGuard.prototype, "canActivate", null);
3200
+ BodyAccessGuard = __decorate23([
3201
+ Injectable12()
3202
+ ], BodyAccessGuard);
3203
+ return createGuard6(BodyAccessGuard);
3204
+ }
3205
+ __name(bodyGuard, "bodyGuard");
3206
+ function makeChainable(decorators) {
3207
+ return Object.assign(() => composeGuards5(...decorators)(), {
3208
+ body(type, field, optional = false) {
3209
+ return makeChainable([...decorators, bodyGuard(type, field, optional)()]);
3210
+ },
3211
+ and(guard) {
3212
+ return makeChainable([...decorators, guard()]);
3213
+ }
3214
+ });
3215
+ }
3216
+ __name(makeChainable, "makeChainable");
3166
3217
  function guards2(resource, options) {
3167
- const resourceType = toSingular(resource);
3168
- return createResourceGuards(GeneratedOwnershipService, resourceType, resource, options);
3218
+ const merged = { adminGuard: defaultAdminGuard, ...options };
3219
+ return createResourceGuards(GeneratedOwnershipService, toSingular(resource), resource, merged);
3169
3220
  }
3170
3221
  __name(guards2, "guards");
3222
+ function for_(resource, options) {
3223
+ const base = guards2(resource, options);
3224
+ return {
3225
+ get read() {
3226
+ return makeChainable([base.canRead()]);
3227
+ },
3228
+ get update() {
3229
+ return makeChainable([base.canUpdate()]);
3230
+ },
3231
+ get create() {
3232
+ return makeChainable([base.canCreate()]);
3233
+ },
3234
+ get delete() {
3235
+ return makeChainable([base.canDelete()]);
3236
+ },
3237
+ get list() {
3238
+ return makeChainable([base.canList()]);
3239
+ }
3240
+ };
3241
+ }
3242
+ __name(for_, "for_");
3171
3243
  return {
3172
3244
  Service: GeneratedOwnershipService,
3173
- guards: guards2
3245
+ for: for_,
3246
+ guards: guards2,
3247
+ bodyGuard
3174
3248
  };
3175
3249
  }
3176
3250
  __name(configureOwnership, "configureOwnership");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [
@@ -62,15 +62,15 @@
62
62
  "typescript": "^5.9.3"
63
63
  },
64
64
  "dependencies": {
65
- "najm-cookies": "^1.1.1",
66
- "najm-core": "^1.1.1",
67
- "najm-database": "^1.1.2",
68
- "najm-guard": "^1.1.1",
69
- "najm-i18n": "^1.1.1",
70
- "najm-cache": "^1.1.1",
71
- "najm-email": "^1.1.1",
72
- "najm-rate": "^1.1.1",
73
- "najm-validation": "^1.1.1",
65
+ "najm-cookies": "workspace:*",
66
+ "najm-core": "workspace:*",
67
+ "najm-database": "workspace:*",
68
+ "najm-guard": "workspace:*",
69
+ "najm-i18n": "workspace:*",
70
+ "najm-cache": "workspace:*",
71
+ "najm-email": "workspace:*",
72
+ "najm-rate": "workspace:*",
73
+ "najm-validation": "workspace:*",
74
74
  "bcryptjs": "^3.0.3",
75
75
  "hono": "^4.0.0",
76
76
  "jsonwebtoken": "^9.0.3",