import { Injectable } from '@nestjs/common'; import { CaslAbilityFactory } from '../authorization/casl-ability.factory'; import { CaslAction, SubjectName } from '../authorization/casl.constants'; import type { AgentToolContext } from '../agent-tools/agent-tool.types'; import type { StudentAccessScope } from './student-access-scope'; /** * Constructs a {@link StudentAccessScope} from the trusted server-side * principal carried in {@link AgentToolContext}. * * ## Scope rules * * | Condition | Scope | * |---|---| * | `isSuperAdmin` | `manageAll` | * | `class:edit` domain ability (`Update Class`) | `manageAll` | * | Everything else | `teacher(userId)` | * * These rules mirror the HTTP-layer logic so agent tools are consistent * with the web dashboard. The ability is constructed fresh from the * principal each time — callers cannot pre-forge it. */ @Injectable() export class StudentAccessScopeFactory { constructor(private readonly abilityFactory: CaslAbilityFactory) {} /** * Build a scope from the authenticated context. * * The ability is constructed from the principal fields inside the context * — every call is a fresh derivation. */ buildScope(context: AgentToolContext): StudentAccessScope { const ability = this.abilityFactory.createForUser({ permissions: context.permissions, isSuperAdmin: context.isSuperAdmin, }); if (context.isSuperAdmin) { return { type: 'manageAll' }; } // class:edit (Update Class) grants full student scope if (ability.can(CaslAction.Update, SubjectName.Class)) { return { type: 'manageAll' }; } return { type: 'teacher', userId: context.userId }; } }