bouncer
bouncer copied to clipboard
Problem with policies when you decide to pass parameters to methods (allows, denies, ...)
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
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 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.
Looking at the signature of the
authorizefunction 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 :
And in my controller I have :
But in the controller, for the denies() I have an error saying "create" should be "never":
@shankiflang What does your config/auth file looks like?
@shankiflang What does your
config/authfile looks like?
Here's my current config/auth. I haven't updated it since it was generated (I only updated the import):
And I've noticed that since the beginning I've had this error:
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
>
}
}
And I've noticed that since the beginning I've had this error:
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
InitializeBouncerMiddlewarelook 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.
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
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
UserandStaffmodels in your auth config, you need to pass them as types in your policy. Otherwise, theGetPolicyMethodsfunction 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
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)
}
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?
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",
This issue should have been fixed already. Could you share a repository with the minimum amount of code to reproduce the issue?
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.
@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.
This issue has become crowded with multiple use-cases. I will close it. Please create separate issues for your use-cases.
