bouncer icon indicating copy to clipboard operation
bouncer copied to clipboard

Problem with policies when you decide to pass parameters to methods (allows, denies, ...)

Open NathaelB opened this issue 1 year ago • 5 comments
trafficstars

Package version

3.1.3

Describe the bug

I'm having a problem when I try to use my policy in my controller. I've created a CompanyPolicy policy with a view method.

async view(payload: JWTPayload, companyId: string) {
  // ...
}

Use in the controller

async show({ params, bouncer }: HttpContext) {
  await bouncer.with(CompanyPolicy).authorize('view', params.id)
}

So I get a TS2554: Expected 1 arguments, but got 2 error. Looking at the signature of the authorize function I have:

function PolicyAuthorizer<never, typeof CompanyPolicy>.authorize<"store">(     action: "store"): Promise<void>

But if I do:

await bouncer.with(CompanyPolicy).authorize('view' as never, params.id)

Now I don't have a problem.

Reproduction repo

https://github.com/libreconnect/backend

NathaelB avatar Aug 02 '24 12:08 NathaelB

I found that only non async policies got the type safety. If you use async function (like you need to query something inside the policies), there would be type errors.

patrickphat avatar Sep 08 '24 07:09 patrickphat

@patrickphat That issue was fixed a long time back here in this release. https://github.com/adonisjs/bouncer/releases/tag/v3.1.3

Regarding the issue @patrickphat is facing seems like a case of misconfiguration. He is used the ctx.bouncer instance, which is configured to pull the user from the ctx.auth. Whereas, the policy first argument accepts JwtPayload. In that case, the error raised by TypeScript is valid. Because ctx.bouncer behind the scenes will pass the ctx.auth.user to the CompanyPolicy.view method, whereas it accepts JwtPayload.

thetutlage avatar Sep 09 '24 04:09 thetutlage

Looking at the signature of the authorize function I have:

function PolicyAuthorizer<never, typeof CompanyPolicy>.authorize<"store">(     action: "store"): Promise<void>

But if I do:

await bouncer.with(CompanyPolicy).authorize('view' as never, params.id)

Now I don't have a problem.

I have the same problem has described here. My policy is defined as : image

And in my controller I have : image

But in the controller, for the denies() I have an error saying "create" should be "never": image

benjibenoit avatar Sep 24 '24 13:09 benjibenoit

@shankiflang What does your config/auth file looks like?

thetutlage avatar Sep 25 '24 01:09 thetutlage

@shankiflang What does your config/auth file looks like?

Here's my current config/auth. I haven't updated it since it was generated (I only updated the import): image

And I've noticed that since the beginning I've had this error: image

benjibenoit avatar Sep 25 '24 09:09 benjibenoit

What's your InitializeBouncerMiddleware look like? Because iirc, it should have this at the end:

declare module '@adonisjs/core/http' {
  export interface HttpContext {
    bouncer: Bouncer<
      Exclude<HttpContext['auth']['user'], undefined>,
      typeof abilities,
      typeof policies
    >
  }
}

ThisIsMissEm avatar Oct 03 '24 16:10 ThisIsMissEm

And I've noticed that since the beginning I've had this error: image

I saw that @RomainLanz had added an export to his repo to remove this error, so I did the same. Like that :

declare module '@adonisjs/auth/types' {
  export interface Authenticators extends InferAuthenticators<typeof authConfig> {}
}

What's your InitializeBouncerMiddleware look like? Because iirc, it should have this at the end:

declare module '@adonisjs/core/http' {
  export interface HttpContext {
    bouncer: Bouncer<
      Exclude<HttpContext['auth']['user'], undefined>,
      typeof abilities,
      typeof policies
    >
  }
}

Yep that's what I have.

benjibenoit avatar Oct 04 '24 09:10 benjibenoit

Hey guys, I have the same problem @shankiflang has described above. For my case it works for some policies and doesn’t work for others, also doesn’t seem to work for async policies (I am using v3.1.3)

UPDATE for anyone reading this thread: After a bit of digging, I found the cause of the problem. If you have multiple authenticators using different models, such as User and Staff models in your auth config, you need to pass them as types in your policy. Otherwise, the GetPolicyMethods function in the Bouncer package will return never.

For example, in your auth config, if you have something like this:

const authConfig = defineConfig({
  default: 'api',
  guards: {
    api: tokensGuard({
      provider: tokensUserProvider({
        tokens: 'accessTokens',
        model: () => import('#models/user'),
      }),
    }),
    staff: tokensGuard({
      provider: tokensUserProvider({
        tokens: 'accessTokens',
        model: () => import('#models/staff'),
      }),
    }),
  },
})

When defining your policies, you need to pass both models as the first argument, like this:

// Notice the User | Staff
edit(user: User | Staff, post: Post): AuthorizerResponse {
  return user.id === post.user.id
}

If you only use user: User or staff: Staff the method will not be inferred correctly. Note that you should also check that user instanceof User or staff instanceof Staff before defining the policy logic

ahmerds avatar Oct 18 '24 17:10 ahmerds

UPDATE for anyone reading this thread: After a bit of digging, I found the cause of the problem. If you have multiple authenticators using different models, such as User and Staff models in your auth config, you need to pass them as types in your policy. Otherwise, the GetPolicyMethods function in the Bouncer package will return never.

Thanks for the research, but I'm only using one authenticator :

const authConfig = defineConfig({
  default: 'web',
  guards: {
    web: sessionGuard({
      useRememberMeTokens: false,
      provider: sessionUserProvider({
        model: () => import('#users/models/user'),
      }),
    }),
  },
})

But I still have the issue

benjibenoit avatar Oct 20 '24 21:10 benjibenoit

Ok, After a bit of digging, I found something. When I put my policies as "async" it doesn't work:

async update(user: User, postId: UUID): Promise<AuthorizerResponse> {
    return await this.is(user, postId)
}

will not work. But this works:

update(user: User, postId: UUID): AuthorizerResponse {
    return this.is(user, postId)
}

benjibenoit avatar Oct 22 '24 13:10 benjibenoit

Ok, After a bit of digging, I found something. When I put my policies as "async" it doesn't work:

async update(user: User, postId: UUID): Promise<AuthorizerResponse> {
    return await this.is(user, postId)
}

will not work. But this works:

update(user: User, postId: UUID): AuthorizerResponse {
    return this.is(user, postId)
}

What version of bouncer do you have in your package.json?

ahmerds avatar Oct 22 '24 15:10 ahmerds

Ok, After a bit of digging, I found something. When I put my policies as "async" it doesn't work:

async update(user: User, postId: UUID): Promise<AuthorizerResponse> {
    return await this.is(user, postId)
}

will not work. But this works:

update(user: User, postId: UUID): AuthorizerResponse {
    return this.is(user, postId)
}

What version of bouncer do you have in your package.json?

"@adonisjs/bouncer": "^3.1.3",

benjibenoit avatar Oct 22 '24 15:10 benjibenoit

This issue should have been fixed already. Could you share a repository with the minimum amount of code to reproduce the issue?

RomainLanz avatar Oct 23 '24 06:10 RomainLanz

This issue should have been fixed already. Could you share a repository with the minimum amount of code to reproduce the issue?

Yeah : https://github.com/shankiflang/repo-repro-bouncer This repo doesn't work as I haven't setup auth, but you can see the never error. In the PostsController, I have on view:

TS2345: Argument of type string is not assignable to parameter of type never

If you remove the async of view policy, the error disappears.

benjibenoit avatar Oct 23 '24 08:10 benjibenoit

@shankiflang

Turns out the issue is with the return type of the PostPolicy you shared. If I remove the return type or update it to async view(user: User): Promise<boolean | AuthorizationResponse> {, everything seems to be work fine.

thetutlage avatar Nov 05 '24 12:11 thetutlage

This issue has become crowded with multiple use-cases. I will close it. Please create separate issues for your use-cases.

thetutlage avatar Nov 05 '24 12:11 thetutlage