Files
gongxue-base/apps/server/src/students/student-access-scope.factory.ts

51 lines
1.7 KiB
TypeScript

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 };
}
}