Nest JS authorization with CASL doesn't work as expected - nest

EXPECTING:
Be able to get user info with id equal to my id only (which is saved in JWT token).
CURRENT RESULT:
I am able to get info about all users with some id.
Used Nest Js docs while creating this solution.
Do appreciate your help.
/casl-ability.factory.ts
type Subjects = InferSubjects<typeof User | typeof Role | 'User'> | 'all';
export type AppAbility = Ability<[Action, Subjects]>;
export class CaslAbilityFactory {
createForUser(userDataFromJWT: JwtAccessTokenInput) {
const { can, cannot, build } = new AbilityBuilder<
Ability<[Action, Subjects]>
>(Ability as AbilityClass<AppAbility>);
// TESTING THIS CASE
can(Action.Read, User, {
id: userDataFromJWT.sub,
});
return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}
private hasRole(roles: unknown[], role: UserRoles): boolean {
return roles.includes(role);
}
}
/getUser.policyHandler.ts
export class GetUserPolicyHandler implements IPolicyHandler {
handle(ability: AppAbility) {
return ability.can(Action.Read, User);
}
}
/types.ts
export enum Action {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
export interface IPolicyHandler {
handle(ability: AppAbility): boolean;
}
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
/policies.guard.ts
#Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const ctx = GqlExecutionContext.create(context);
const { user }: { user: JwtAccessTokenInput } = ctx.getContext().req;
const ability = this.caslAbilityFactory.createForUser(user);
return policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability),
);
}
private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
if (typeof handler === 'function') {
return handler(ability);
}
return handler.handle(ability);
}
}
user.resolver.ts
#Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
#Query(() => User, { name: 'user' })
#UseGuards(PoliciesGuard)
#CheckPolicies(new GetUserPolicyHandler())
#UseInterceptors(UserNotExistsByIDInterceptor)
async findOne(#Args('id', { type: () => Int }) id: number): Promise<User> {
return await this.userService.findOne(id);
}
}

possible duplicate of NestJS + CASL + Mongoose: CASL cannot infer subject type from Mongoose Schema
if you're using mongoose you need to inject the model to allow InferSubjects to retrieve the type thus allowing you to use filters and fields.

Related

Delete item from pinia state

I am new to vue and I have just started using pinia. I wanna delete an item from array but it does not work
here is my store
import {defineStore} from 'pinia'
export interface ObjectDto {
input: string,
}
interface ObjectDtoInterface {
objects: Array<ObjectDto>
}
export const useSearchHistoryStore = defineStore('objectsStore', {
state: (): ObjectDtoInterface => {
return {
objects: [] as ObjectDto[]
}
},
actions: {
add(dto: ObjectDto) {
if (this.objects
.filter(shd => dto.input === shd.input)
.length === 0) {
this.objects.unshift(dto)
}
},
delete(obj: ObjectDto) {
this.objects = this.objects.filter(e => !(e.input === obj.input))
}
}
})
and here is the function from different .ts file
function delete(obj: ObjectDto) {
objectsStore.delete(obj)
}
add action works perfect, it adds item to the state but when I try to delete an item, nothing happens. The data I pass to delete method is 100% good because I checked this many times
Filter does not mutate the original object, you need to reasing
delete(obj: ObjectDto) {
this.objects = this.objects.filter(e => !(e.input === obj.input))
}
more info https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

How to use graphql dataloader loadMany function properly in nestjs?

I want to use dataloader loadMany function in nestjs. Can any help me to use it properly-
dataloader.service.ts-
#Injectable()
export class DataloaderService {
constructor(private readonly categoryService: CategoryService) { }
createLoaders(): IDataloaders {
const subCategoryLoader = new DataLoader<ObjectId, Subcategory>(
async (keys: readonly ObjectId[]) =>
this.categoryService.findSubCategoryByBatch(keys as ObjectId[])
);
return {
subCategoryLoader
};
}
}
Then I category service(findSubCategoryByBatch)-
async findSubCategoryByBatch(Ids: ObjectId[]): Promise<(Subcategory | Error)[]> {
const categories = await this.subCategoryModel.find({ _id: { $in: Ids } });
const mappedResults = Ids.map(
(id) =>
categories.find((result) => result.id === id) ||
new Error(`Could not load owner ${id}`),
);
return mappedResults;
}
Then I call it -
#ResolveField('subCategory', () => [Subcategory])
getSubCategory(
#Parent() category: Category,
#Context() { loaders }: IGraphQLContext
) {
return loaders.subCategoryLoader.loadMany(category.subCategory)
}
Notice: I use loadMany function because subCategory is a array IDs.
But I am getting error. Here How to write function for loadMany function. Please help me. I need it. Please help me. please help me.

click event in custom plugin ckeditor5

I have a custom plugin in ckeditor5.
When user click on toolbar icon my plugin convert selected test to custom element with a custom attribute name comment-id.
this work properly.
Now I want to watch on click element and get comment-id on click and I don't know how can I do that.
this is the code of my custom plugin
import uploadIcon from './message.svg'
import ButtonView from '#ckeditor/ckeditor5-ui/src/button/buttonview'
import Plugin from '#ckeditor/ckeditor5-core/src/plugin'
import Command from '#ckeditor/ckeditor5-core/src/command'
import './comment.css'
export default class CustomFileExporerPlugin extends Plugin {
init() {
const editor = this.editor
const config = editor.config.get('comment')
console.log(editor.editing.view.document.isFocused)
editor.editing.view.document.on('click', a => {
console.log(a)
})
editor.model.schema.extend('$text', { allowAttributes: 'comment' })
editor.conversion.attributeToElement({
model: 'comment',
view: (commentId, writer) => {
debugger
if (writer) {
return writer.writer.createAttributeElement(
'comment',
{
'comment-id': commentId,
class: `ck-comment-marker`
},
{ priority: 5 }
)
}
}
})
editor.commands.add('comment', new CommentCommand(editor))
editor.ui.componentFactory.add('comment', locale => {
const view = new ButtonView(locale)
view.set({
label: 'add comment',
icon: uploadIcon,
tooltip: true
})
view.on('execute', async () => {
editor.editing.view.focus()
let id = await config.callback()
editor.execute('comment', { value: 'comment', id })
})
return view
})
}
}
class CommentCommand extends Command {
refresh() {
const model = this.editor.model
const doc = model.document
this.value = doc.selection.getAttribute('comment')
this.isEnabled = model.schema.checkAttributeInSelection(
doc.selection,
'comment'
)
}
execute(options = {}) {
const model = this.editor.model
const document = model.document
const selection = document.selection
const highlighter = options.value
model.change(writer => {
const ranges = model.schema.getValidRanges(
selection.getRanges(),
'comment'
)
if (selection.isCollapsed) {
const position = selection.getFirstPosition()
if (selection.hasAttribute('comment')) {
const isSameHighlight = value => {
return (
value.item.hasAttribute('comment') &&
value.item.getAttribute('comment') === this.value
)
}
const highlightStart = position.getLastMatchingPosition(
isSameHighlight,
{ direction: 'backward' }
)
const highlightEnd = position.getLastMatchingPosition(isSameHighlight)
const highlightRange = writer.createRange(
highlightStart,
highlightEnd
)
writer.removeAttribute('comment', highlightRange)
writer.removeSelectionAttribute('comment')
} else if (highlighter) {
writer.setSelectionAttribute('comment', highlighter)
}
} else {
for (const range of ranges) {
writer.setAttribute('comment', options, range)
}
}
})
}
}
When rendering editor, you can catch custom plugin click event using below code.
const command = editor.commands.get('comment')
command.on('execute', () => { catch click event in custom plugin})

Angular 5 - Event emitter (Property 'update' does not exist on type ....)

I've got a component that I want to update when a person's name changes by emitting an event. My problem is the code doesn't compile because of an error. This is my code
ApplicationFormComponent
#Output() nameChange = new EventEmitter();
closeAccordion(isComplete: string, accordionToClose: string, accordion: NgbAccordion) {
if (accordionToClose === 'personal-details-panel') {
this.applicationStatusFlags.personalDetailsStatus = (isComplete === 'true');
this.nameChange.emit({ personId: this.personId });
}
}
ApplicationFormComponent.html
<name-display
[personId]="personId"
[placeHolderText]="'Hello'"
(nameChange)="update($event)">
</name-display>
NameDisplayComponent
import { Component, Input, OnChanges, SimpleChanges } from '#angular/core';
import { PersonService } from "../../../service/person.service";
#Component({
selector: 'name-display',
templateUrl: './NameDisplay.component.html',
providers: [PersonService]
})
export class NameDisplayComponent implements OnChanges {
constructor(private readonly personService: PersonService) { }
#Input() personId;
#Input() placeHolderText: string = "";
forename: string = "";
ngOnChanges(changes: SimpleChanges): void {
if (changes["personId"]) {
this.personService.getPersonDetails(this.personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
};
update(personId: number) {
alert("update name");
this.personService.getPersonDetails(personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
}
My problem is basically when I use angular cli with the command ng server --aot, it doesn't compile because of this error:
ERROR in src\app\component\ApplicationForm\ApplicationForm.component.html(42,9): : Property 'update' does not exist on type 'ApplicationFormComponent'.
I've written a similar component that uses an event emitter which doesn't have this problem, so I'm stuck with how to fix the error.
Any ideas?
It is because you are passing $event to method.
(nameChange)="update($event)"
But it accepts number.
update(personId: number) {
alert("update name");
}
Please change the method as below.
update(event:any) {
const personId = event as number
alert("update name");
}

Angular 5 HTTPClient not returning results for RouteResolver

I have to say HttpClient Observables, subscriptions etc are pretty hard/time consuming to get right.
I have been working on a problem for a while now and tearing my hair out. I have a service that I need to be able to perform a mapping function on.
loadAllSummary(organisationId: number) {
return this.http.get('/api/aircrafts/organisations/' + organisationId)
.pipe(
map(data => data.forEach(datum => {
console.log('why am i not getting here! ' + JSON.stringify(data));
return this.mapToSummary(datum);
}))
);
}
with the mapToSummary() method:
private mapToSummary(aircraft: Aircraft): IAircraftSummary {
const lastDate: Date = new Date(Math.max.apply(null, aircraft.workorders.map(function(e) {
return new Date(e.date);
})));
return new AircraftSummary({
lastWork: lastDate,
rego: aircraft.registration,
make: aircraft.make,
model: aircraft.model,
contact: (aircraft.owner.type.endsWith('primary')) ? aircraft.owner.principal : aircraft.operator.principal,
phone: (aircraft.owner.type.endsWith('primary')) ? aircraft.owner.contact.phone : aircraft.operator.contact.phone
});
}
Now, I need these summaries as input data to a view, so I borrowed code from the interwebs and created this ResolverService:
#Injectable()
export class AircraftsResolverService implements Resolve<IAircraftSummary[]> {
constructor(private service: AircraftService,
private router: Router) { }
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IAircraftSummary[]> {
console.log('called AircraftsResolverService')
const id = route.params['id'];
if (isNaN(+id)) {
console.log(`Organisation id was not a number: ${id}`);
this.router.navigate(['/login']);
return Observable.of(null);
}
return this.service.loadAllSummary(+id)
.map(summaries => {
console.log(summaries)
if (summaries) {
return summaries;
}
console.log(`Summaries were not found: ${id}`);
this.router.navigate(['/organisations/', +id]);
return null;
})
.catch(error => {
console.log(`Retrieval error: ${error}`);
this.router.navigate(['/organisations/', +id]);
return Observable.of(null);
});
}
}
Which I then refer to in the ngOnInit call...
ngOnInit() {
this.currentUser = this.authenticationService.returnCurrentUser();
this.route.data
.subscribe(({aircrafts}) => {
this.aircrafts = aircrafts;
const id = +this.route.snapshot.paramMap.get('id');
console.log(' where are my aircraft!' + JSON.stringify(aircrafts));
this.ELEMENT_DATA = aircrafts;
this.displayedColumns = ['Last Work', 'Rego', 'Make', 'Model', 'Contact', 'Phone'];
this.dataSource = new MatTableDataSource(this.ELEMENT_DATA);
this.dataSource.sort = this.sort;
console.log(id);
if (id) {
this.organisationService.getById(id).subscribe(org => {
this.organisation = org;
});
} else {
console.log('its bad');
}
});
console.log(this.dataSource);
}
The console log under the subscribe is undefined and the console.logs under the service never get triggered. So once again, I find myself not understanding why subscription fire or not fire, or whatever it is that they do.
How do I get past this? thanks everyone.
EDIT: appears that the problem is actually in the ResolverService, I have been able to determine that the data service is getting the results and that they are correct. For some reason, the resolver service can't see them.
The answer was in the route resolver, or rather the app-routing-module. I should have included it in the question, because some of the angular saltys would have picked it up
I was trying to do this:.
{ path: 'organisations/:orgId/aircrafts/:id', component: AircraftsComponent, resolve: {aircrafts : AircraftsResolverService}, canActivate: [AuthGuard] },
But you can't, you have to do this:
{ path: 'organisations/aircrafts/:orgId/:id', component: AircraftsComponent, resolve: {aircrafts : AircraftsResolverService}, canActivate: [AuthGuard] },
results in very non-resty urls, but, hey, whatever works, right?