Search code examples
phpgraphqllumenlaravel-lighthouse

Lighthouse GraphQL data ownership via user_id


I have an application that allows for multiple users. Each user is completely isolated from each other; this means that everything that's not a user in the database has a user_id column and only the logged-in user is allowed to view, update, or delete them. In addition, users cannot create rows with someone else's user_id.

Is there a built-in way to solve this with Lumen/Lighthouse? Here's what I've done, and it works, but I'm wondering if I've re-invented the wheel:

  1. Every Model has a user relationship, like this:
public function user(): BelongsTo
{
    return $this->belongsTo(User::class);
}
  1. I've added a HasOwnerTrait to these models, with the following contents:
public static function boot()
{
    parent::boot();

    static::creating(function (Model $model) {
        $model->user_id = Auth::user()->id;
    });

    static::saving(function (Model $model) {
        if ($model->user_id !== Auth::user()->id) {
            $exception = new ModelNotFoundException();
            $exception->setModel(self::class, $model->id);
            throw $exception;
        }
    });

    static::deleting(function (Model $model) {
        if ($model->user_id !== Auth::user()->id) {
            $exception = new ModelNotFoundException();
            $exception->setModel(self::class, $model->id);
            throw $exception;
        }
    });
}

public function scopeIsOwner($query)
{
    return $query->where('user_id', Auth::user()->id);
}
  1. And finally, in my schema definition:
type Query {
    recipes: [Recipe!]! @all(scopes: ["isOwner"])
}

type Mutation {
    createRecipe(input: CreateRecipeInput! @spread): Recipe @create
    updateRecipe(id: ID!, input: UpdateRecipeInput! @spread): Recipe @update
    deleteRecipe(id: ID!): Recipe @delete
}

Again, this is working, but does it need to be ad hoc like this, or is there a better way?


Solution

  • I think your solution is fine, it saves writing a whole bunch of boilerplate. A few minor suggestions:

    You can make your trait into a bootable trait that is automatically called by the framework by renaming the boot method to bootHasOwnerTrait.

    Maybe consider making the isOwner scope active by default. In Laravel, this is confusingly called a global scope. That allows you to omit naming the scope explicitly, although you can still omit it if you have some queries where it should not apply, e.g. statistics.