Skip to content

Commit

Permalink
Add new defaultStrategy option to scope-auth plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
hayes committed Jul 20, 2023
1 parent 5d3f7b9 commit 09bd29a
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-squids-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pothos/plugin-scope-auth': minor
---

Add new defaultStrategy option to allow enforcing all scopes in a scopeMap without using `$all`
29 changes: 26 additions & 3 deletions packages/plugin-scope-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ For example, if you want to re-throw errors thrown by authorization functions yo
writeing a custom `unauthorizedError` callback like this:

```typescript
import SchemaBuilder, { AuthFailure, AuthScopeFailureType } from '@pothos/core';
import SchemaBuilder from '@pothos/core';
import ScopeAuthPlugin, { AuthFailure, AuthScopeFailureType } from '@pothos/plugin-scope-auth';

// Find the first error and re-throw it
function throwFirstError(failure: AuthFailure) {
Expand All @@ -380,7 +381,7 @@ function throwFirstError(failure: AuthFailure) {
failure.kind === AuthScopeFailureType.AllAuthScopes
) {
for (const child of failure.failures) {
throwFirstError(child, recursive);
throwFirstError(child);
}
}
}
Expand All @@ -397,7 +398,7 @@ const builder = new SchemaBuilder<{
// throw an error if it's found
throwFirstError(result.failure);
// throw a fallback error if no error was found
new Error(`Not authorized`);
return new Error(`Not authorized`);
},
},
plugins: [ScopeAuthPlugin],
Expand Down Expand Up @@ -578,6 +579,28 @@ above example requires a request to have either the `employee` or `deferredScope
`public` scope. `$any` and `$all` each take a scope map as their parameters, and can be nested
inside each other.

You can change the default strategy used for top level auth scopes by setting the `defaultStrategy`
option in the builder (defaults to `any`):

```typescript
const builder = new SchemaBuilder<{
Context: {
user: User | null;
};
AuthScopes: {
loggedIn: boolean;
};
}>({
plugins: [ScopeAuthPlugin],
scopeAuthOptions: {
defaultStrategy: 'all',
},
authScopes: async (context) => ({
loggedIn: !!context.user,
}),
});
```

### Auth that depends on parent value

For cases where the required scopes depend on the value of the requested resource you can use a
Expand Down
7 changes: 5 additions & 2 deletions packages/plugin-scope-auth/src/request-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ export default class RequestCache<Types extends SchemaTypes> {

treatErrorsAsUnauthorized: boolean;

defaultStrategy: 'all' | 'any';

constructor(builder: PothosSchemaTypes.SchemaBuilder<Types>, context: Types['Context']) {
this.builder = builder;
this.context = context;
this.cacheKey = builder.options.scopeAuthOptions?.cacheKey;
this.treatErrorsAsUnauthorized =
builder.options.scopeAuthOptions?.treatErrorsAsUnauthorized ?? false;
this.defaultStrategy = builder.options.scopeAuthOptions?.defaultStrategy ?? 'any';
}

static fromContext<T extends SchemaTypes>(
Expand Down Expand Up @@ -294,7 +297,7 @@ export default class RequestCache<Types extends SchemaTypes> {
}

if ($any) {
const anyResult = this.evaluateScopeMap($any, info);
const anyResult = this.evaluateScopeMap($any, info, false);

if (isThenable(anyResult)) {
promises.push(anyResult);
Expand Down Expand Up @@ -374,7 +377,7 @@ export default class RequestCache<Types extends SchemaTypes> {
evaluateScopeMap(
map: AuthScopeMap<Types> | boolean,
info?: GraphQLResolveInfo,
forAll = false,
forAll = this.defaultStrategy === 'all',
): MaybePromise<null | AuthFailure> {
if (typeof map === 'boolean') {
return map
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-scope-auth/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ScopeAuthPluginOptions<Types extends SchemaTypes> {
runScopesOnType?: boolean;
treatErrorsAsUnauthorized?: boolean;
authorizeOnSubscribe?: boolean;
defaultStrategy?: 'all' | 'any';
}

export interface BuiltInScopes<Types extends SchemaTypes> {
Expand Down
22 changes: 22 additions & 0 deletions website/pages/docs/plugins/scope-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,28 @@ above example requires a request to have either the `employee` or `deferredScope
`public` scope. `$any` and `$all` each take a scope map as their parameters, and can be nested
inside each other.

You can change the default strategy used for top level auth scopes by setting the `defaultStrategy`
option in the builder (defaults to `any`):

```typescript
const builder = new SchemaBuilder<{
Context: {
user: User | null;
};
AuthScopes: {
loggedIn: boolean;
};
}>({
plugins: [ScopeAuthPlugin],
scopeAuthOptions: {
defaultStrategy: 'all',
},
authScopes: async (context) => ({
loggedIn: !!context.user,
}),
});
```

### Auth that depends on parent value

For cases where the required scopes depend on the value of the requested resource you can use a
Expand Down

0 comments on commit 09bd29a

Please sign in to comment.