#Reusable author guard

1 messages · Page 1 of 1 (latest)

sour haven
#

Hey folks! I need to build a reusable delete guard which will basically don't allow anyone but admin or an author of a specific document to delete that document. This is how my guard looks like for a note:

@Injectable()
export class DeleteGuard implements CanActivate {
  constructor(
    private readonly notesService: NotesService,
    private readonly i18n: I18nService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request>();

    const { user } = request;

    // If user is an admin, they can delete all notes
    if (user.role === 'ADMIN') {
      return true;
    }

    // If user is not an admin, we have to fetch the note to determine if they can delete it
    const note = await this.notesService.findOne(request.params.id);

    // Users can delete their own notes
    if (user.id === note.author.id) {
      return true;
    }

    throw new UnauthorizedException('You cannot delete this note');
  }
}

Now, I need the same thing for all other kinds of documents in my app (there's like 20 of them). How would you make that guard reusable considering that for each kind of document I'd need to call a method from a service specific to that document type in order to determine who the author of the document is? Thank you upfront!

vestal sigil
#

Does every service have this findOne method and does every entity have this .author.id property?

sour haven
#

Yes, we can assume it does. Or at least it needs to if it's supposed to work at all right?

vestal sigil
#

Yeah, that will help. I'll explain what I'm thinking when I'm back at my computer. A bit much to explain via mobile

vestal sigil
#

Okay! So my thought is you create a common injection token that all modules define, something like

{
  provide:  'FeatureService',
  useExisting: NotesService,
}

Where you change the useExisting's value per module. Then in the guard you can do @Inject('FeatureService') and always get the current module's feature service without having to change the constructor. Now create an interface for the FeatureService that says you'll always have a findOne method that returns an object that has .author.id and you'll be golden:)

sour haven
#

Oh, that looks neat, thank you very much. I’d need to do some reading in order to understand how it works exactly. It feels useful to also do some runtime check to ensure method we wanna call actually exists on the service, don’t you think?

But that looks really promising, I started to think that it’s not worth making reusable after all since I generally prefer readability and being explicit over being super DRY but with your suggestion it started to make sense again:)

lyric crow
#

claim based permissions > role based permissions
change my mind

this way you can check directly for a particular permission
rather then a role

sour haven
#

Can you elaborate a little? My understanding is that I'm using a mix of both worlds - author part being the CBAC part and admin as RBAC. And the author part is harder to implement.