Browse Source

feat: Add @auth directive to graphql

style/lint-staged
Dale 3 weeks ago
parent
commit
5ecbab71f6
Signed by: Deiru GPG Key ID: AA250C0277B927E1
  1. 105
      graphql.schema.json
  2. 2
      package.json
  3. 72
      src/server/graphql/directives/auth.tsx
  4. 88
      src/server/graphql/index.ts
  5. 19
      src/server/graphql/mutations/activities/index.ts
  6. 10
      src/server/graphql/mutations/users/index.ts
  7. 17
      src/server/graphql/resolvers/stream-keys/index.ts
  8. 4
      src/server/graphql/schema/Activities.graphql
  9. 4
      src/server/graphql/schema/Channels.graphql
  10. 6
      src/server/graphql/schema/Directives.graphql
  11. 7
      src/server/graphql/schema/Roles.graphql
  12. 8
      src/server/graphql/schema/StreamKeys.graphql
  13. 8
      src/server/graphql/schema/User.graphql
  14. 31
      src/shared/graphql.ts
  15. 30
      yarn.lock

105
graphql.schema.json

@ -37,6 +37,18 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "roles",
"description": null,
"args": [],
"type": {
"kind": "ENUM",
"name": "AccessUnit",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "users",
"description": null,
@ -431,26 +443,46 @@
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "CacheControlScope",
"kind": "INPUT_OBJECT",
"name": "AuthRightConfig",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
"inputFields": [
{
"name": "PUBLIC",
"name": "unit",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "AccessUnit",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "PRIVATE",
"name": "subject",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
@ -1485,13 +1517,9 @@
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -1518,13 +1546,9 @@
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -2903,16 +2927,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Int",
"description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Schema",
@ -3810,34 +3824,41 @@
],
"directives": [
{
"name": "cacheControl",
"name": "auth",
"description": null,
"isRepeatable": false,
"locations": [
"FIELD_DEFINITION",
"OBJECT",
"INTERFACE"
"FIELD_DEFINITION"
],
"args": [
{
"name": "maxAge",
"name": "roles",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "scope",
"name": "rights",
"description": null,
"type": {
"kind": "ENUM",
"name": "CacheControlScope",
"ofType": null
"kind": "LIST",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AuthRightConfig",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,

2
package.json

@ -23,6 +23,8 @@
"@chakra-ui/react": "^1.6.5",
"@emotion/react": "^11.4.0",
"@emotion/styled": "^11.3.0",
"@graphql-tools/schema": "^8.1.2",
"@graphql-tools/utils": "^8.1.2",
"@types/apollo-upload-client": "^14.1.0",
"@types/graphql-upload": "^8.0.6",
"@types/node": "^16.3.3",

72
src/server/graphql/directives/auth.tsx

@ -0,0 +1,72 @@
import { getDirective, MapperKind, mapSchema } from "@graphql-tools/utils";
import { defaultFieldResolver, GraphQLFieldConfig } from "graphql";
import { ResolverContext } from "miracle-tv-server/types/resolver";
import { all, any, identity, prop } from "ramda";
import { checkRight } from "miracle-tv-server/db/acl/roles";
import { ServerError } from "miracle-tv-server/graphql/errors/general";
import {
AuthenticationError,
AuthorizationError,
} from "miracle-tv-server/graphql/errors/auth";
const roleGuard =
(schema: any, directiveName: string) =>
(fieldConfig: GraphQLFieldConfig<any, any>) => {
const upperDirective = getDirective(
schema,
fieldConfig,
directiveName
)?.[0];
if (upperDirective) {
// Get this field's original resolver
const { resolve = defaultFieldResolver } = fieldConfig;
// Replace the original resolver with a function that *first* calls
// the original resolver, then converts its result to upper case
fieldConfig.resolve = async function (
source,
args,
context: ResolverContext,
info
) {
if (!context.user) {
throw new AuthenticationError();
}
const roles = upperDirective["roles"]?.filter(identity) || [];
const rights = upperDirective["rights"]?.filter(identity) || [];
if (roles.length === 0 && rights.length === 0) {
throw new ServerError(
"Misconfigured access directive: Must have either roles, rights or both parameters set."
);
}
const hasRoles =
roles.length > 0
? any(
(role) => roles.includes(role),
context.userRoles.map(prop("name"))
)
: true;
const hasRights =
rights.length > 0
? any((right: any) => {
return checkRight(context.userRoles, right.unit, right.subject);
}, rights)
: true;
if (hasRoles && hasRights) {
return await resolve(source, args, context, info);
} else {
throw new AuthorizationError();
}
};
return fieldConfig;
}
};
export function authDirective(schema: any, directiveName: string) {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: roleGuard(schema, directiveName),
[MapperKind.QUERY_ROOT_FIELD]: roleGuard(schema, directiveName),
[MapperKind.MUTATION_ROOT_FIELD]: roleGuard(schema, directiveName),
});
}

88
src/server/graphql/index.ts

@ -1,5 +1,5 @@
import { ApolloServer } from "apollo-server-express";
import { gql } from "apollo-server";
import { gql, makeExecutableSchema } from "apollo-server";
import { GraphQLUpload } from "graphql-upload";
import glob from "glob";
import path from "path";
@ -58,6 +58,7 @@ import { FilesModel } from "miracle-tv-server/db/models/Files";
import { fileResolvers } from "./resolvers/file";
import { red } from "chalk";
import { DbSession, DbUser } from "miracle-tv-server/db/models/types";
import { authDirective } from "miracle-tv-server/graphql/directives/auth";
const schemaString = glob
.sync(path.resolve(__dirname, "./**/*.graphql"))
@ -70,52 +71,55 @@ export const schema = gql`
${schemaString}
`;
const resolvers: Resolvers<ResolverContext> = {
Upload: GraphQLUpload,
Query: {
info: () => ({
name: `${config.name || "Miracle TV"}`,
version: process.env.npm_package_version || "none",
packageName: process.env.npm_package_name || "none",
}),
user: userQueryResolver,
users: usersQueryResolver,
channel: channelQueryResolver,
channels: channelsQueryResolver,
activity: activityQueryResolver,
activities: activitiesQueryResolver,
streamKeys: streamKeysQueryResolver,
self: userSelfQueryResolver,
selfStreamKeys: selfStreamKeysQueryResolver,
test: userTestQueryResolver,
...fileResolvers,
},
Mutation: {
ping: (...args) => {
args;
return "pong";
let executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers: {
Upload: GraphQLUpload,
Query: {
info: () => ({
name: `${config.name || "Miracle TV"}`,
version: process.env.npm_package_version || "none",
packageName: process.env.npm_package_name || "none",
}),
user: userQueryResolver,
users: usersQueryResolver,
channel: channelQueryResolver,
channels: channelsQueryResolver,
activity: activityQueryResolver,
activities: activitiesQueryResolver,
streamKeys: streamKeysQueryResolver,
self: userSelfQueryResolver,
selfStreamKeys: selfStreamKeysQueryResolver,
test: userTestQueryResolver,
...fileResolvers,
},
Mutation: {
ping: () => {
return "pong";
},
...userMutations,
...fileMutations,
signIn: signInMutation,
createChannel: createChannelMutation,
updateChannel: updateChannelMutation,
createActivity: createActivityMutaiton,
updateActivity: updateActivityMutation,
createStreamKey: createStreamKeyMutation,
revokeStreamKey: revokeStreamKeyMutation,
},
...userMutations,
...fileMutations,
signIn: signInMutation,
createChannel: createChannelMutation,
updateChannel: updateChannelMutation,
createActivity: createActivityMutaiton,
updateActivity: updateActivityMutation,
createStreamKey: createStreamKeyMutation,
revokeStreamKey: revokeStreamKeyMutation,
User: userResolver,
Channel: channelResolver,
Activity: activityResolver,
Role: roleResolvers,
StreamKey: streamKeysResolver,
},
User: userResolver,
Channel: channelResolver,
Activity: activityResolver,
Role: roleResolvers,
StreamKey: streamKeysResolver,
};
});
executableSchema = authDirective(executableSchema, "auth");
export const graphqlEndpoint = new ApolloServer({
uploads: false,
typeDefs: schema,
resolvers,
schema: executableSchema,
introspection: true,
playground: true,
formatError: (err) => {

19
src/server/graphql/mutations/activities/index.ts

@ -2,23 +2,14 @@ import {
AuthenticationError,
AuthorizationError,
} from "miracle-tv-server/graphql/errors/auth";
import { AccessUnit, MutationResolvers } from "miracle-tv-shared/graphql";
import { MutationResolvers } from "miracle-tv-shared/graphql";
import { ResolverContext } from "miracle-tv-server/types/resolver";
import { any } from "ramda";
export const createActivityMutaiton: MutationResolvers<ResolverContext>["createActivity"] =
async (_, { input }, { db: { activities }, user, userRoles }) => {
if (!user) {
throw new AuthenticationError();
}
if (
!any(
(role) => role.access.rights.activities === AccessUnit.Write,
userRoles
)
) {
throw new AuthorizationError();
}
return await activities.createActivity(input);
};
@ -27,13 +18,5 @@ export const updateActivityMutation: MutationResolvers<ResolverContext>["updateA
if (!user) {
throw new AuthenticationError();
}
if (
!any(
(role) => role.access.rights.activities === AccessUnit.Write,
userRoles
)
) {
throw new AuthorizationError();
}
return await activities.updateActivity(input);
};

10
src/server/graphql/mutations/users/index.ts

@ -13,20 +13,10 @@ export const userMutations: UserMutationResolvers = {
return await users.createUserSafe(input);
},
async updateSelf(_, { input }, { db: { users }, user }) {
if (!user) {
throw new AuthenticationError();
}
const id = user?.id;
return users.updateUser({ id, ...input }) as any;
},
async updateUser(_, { input }, { db: { users }, user, userRoles }) {
if (!user) {
throw new AuthenticationError();
}
const hasRight = checkRight(userRoles, AccessUnit.Write, "users");
if (!hasRight && input.id !== user.id) {
throw new AuthorizationError();
}
return users.updateUser(input) as any;
},
};

17
src/server/graphql/resolvers/stream-keys/index.ts

@ -9,25 +9,12 @@ import { ResolverContext } from "miracle-tv-server/types/resolver";
export const streamKeysQueryResolver: QueryResolvers<ResolverContext>["streamKeys"] =
async (_, _args, { userRoles, db: { streamKeys } }) => {
if (
checkRight(userRoles, AccessUnit.Write, "streamKeys") ||
checkRight(userRoles, AccessUnit.Read, "streamKeys")
) {
return streamKeys.getStreamKeys() as any;
}
throw new AuthorizationError();
return streamKeys.getStreamKeys() as any;
};
export const selfStreamKeysQueryResolver: QueryResolvers<ResolverContext>["selfStreamKeys"] =
async (_, _args, { user, userRoles, db: { streamKeys } }) => {
if (
checkRight(userRoles, AccessUnit.Self, "streamKeys") ||
checkRight(userRoles, AccessUnit.Write, "streamKeys") ||
checkRight(userRoles, AccessUnit.Read, "streamKeys")
) {
return streamKeys.getStreamKeys({ userId: user.id }) as any;
}
throw new AuthorizationError();
return streamKeys.getStreamKeys({ userId: user.id }) as any;
};
export const streamKeysResolver: StreamKeyResolvers<ResolverContext> = {

4
src/server/graphql/schema/Activities.graphql

@ -35,6 +35,6 @@ extend type Query {
}
extend type Mutation {
createActivity(input: CreateActivityInput): Activity!
updateActivity(input: UpdateActivityInput): Activity!
createActivity(input: CreateActivityInput): Activity! @auth(rights: [{ unit: WRITE, subject: "activities" }])
updateActivity(input: UpdateActivityInput): Activity! @auth(rights: [{ unit: WRITE, subject: "activities" }])
}

4
src/server/graphql/schema/Channels.graphql

@ -38,6 +38,6 @@ input UpdateChannelInput {
}
extend type Mutation {
createChannel(input: CreateChannelInput): Channel!
updateChannel(input: UpdateChannelInput): Channel!
createChannel(input: CreateChannelInput): Channel! @auth(rights: [{ unit: WRITE, subject: "channels" }, { unit: SELF, subject: "channels" }])
updateChannel(input: UpdateChannelInput): Channel! @auth(rights: [{ unit: WRITE, subject: "channels" }, { unit: SELF, subject: "channels" }])
}

6
src/server/graphql/schema/Directives.graphql

@ -0,0 +1,6 @@
input AuthRightConfig {
unit: AccessUnit!
subject: String!
}
directive @auth(roles: [ID], rights: [AuthRightConfig]) on OBJECT | FIELD_DEFINITION

7
src/server/graphql/schema/Roles.graphql

@ -19,6 +19,7 @@ type Actions {
type AccessRights {
channels: AccessUnit
streamKeys: AccessUnit
roles: AccessUnit
users: AccessUnit
activities: AccessUnit
}
@ -41,9 +42,9 @@ extend type Query {
}
extend type Mutation {
createRole(input: CreateRoleInput): Role!
updateRole(input: UpdateRoleInput): Role!
deleteRole(id: ID!): Boolean!
createRole(input: CreateRoleInput): Role! @auth(rights: [{ unit: WRITE, subject: "roles" }])
updateRole(input: UpdateRoleInput): Role! @auth(rights: [{ unit: WRITE, subject: "roles" }])
deleteRole(id: ID!): Boolean! @auth(rights: [{ unit: WRITE, subject: "roles" }])
}
########################

8
src/server/graphql/schema/StreamKeys.graphql

@ -5,8 +5,8 @@ type StreamKey {
}
extend type Query {
streamKeys: [StreamKey]!
selfStreamKeys: [StreamKey]!
streamKeys: [StreamKey]! @auth(rights: [{ unit: WRITE, subject: "streamKeys" }])
selfStreamKeys: [StreamKey]! @auth(rights: [{ unit: SELF, subject: "streamKeys" }])
}
input CreateStreamKeyInput {
@ -15,6 +15,6 @@ input CreateStreamKeyInput {
}
extend type Mutation {
createStreamKey(input: CreateStreamKeyInput): StreamKey!
revokeStreamKey(input: CreateStreamKeyInput): Boolean!
createStreamKey(input: CreateStreamKeyInput): StreamKey! @auth(rights: [{ unit: SELF, subject: "streamKeys" }, { unit: WRITE, subject: "streamKeys" }])
revokeStreamKey(input: CreateStreamKeyInput): Boolean! @auth(rights: [{ unit: SELF, subject: "streamKeys" }, { unit: WRITE, subject: "streamKeys" }])
}

8
src/server/graphql/schema/User.graphql

@ -30,8 +30,8 @@ type UserSettings {
extend type Query {
users: [User]!
self: User!
user(id: ID!): User
self: User! @auth(rights: [{ unit: SELF, subject: "users" }])
user(id: ID!): User @auth(rights: [{ unit: READ, subject: "users" }])
}
input CreateUserInput {
@ -67,6 +67,6 @@ input UpdateUserInput {
extend type Mutation {
signUp(input: CreateUserInput!): User!
signIn(input: SignInInput!): SessionResponse
updateUser(input: UpdateUserInput!): User!
updateSelf(input: UpdateSelfInput!): User!
updateUser(input: UpdateUserInput!): User @auth(rights: [{ unit: WRITE, subject: "users" }])
updateSelf(input: UpdateSelfInput!): User @auth(rights: [{ unit: SELF, subject: "users" }])
}

31
src/shared/graphql.ts

@ -21,6 +21,7 @@ export type AccessRights = {
__typename?: 'AccessRights';
channels?: Maybe<AccessUnit>;
streamKeys?: Maybe<AccessUnit>;
roles?: Maybe<AccessUnit>;
users?: Maybe<AccessUnit>;
activities?: Maybe<AccessUnit>;
};
@ -72,10 +73,10 @@ export type ActivityFilter = {
verb?: Maybe<Scalars['String']>;
};
export enum CacheControlScope {
Public = 'PUBLIC',
Private = 'PRIVATE'
}
export type AuthRightConfig = {
unit: AccessUnit;
subject: Scalars['String'];
};
export type Channel = {
__typename?: 'Channel';
@ -160,8 +161,8 @@ export type Mutation = {
revokeStreamKey: Scalars['Boolean'];
signUp: User;
signIn?: Maybe<SessionResponse>;
updateUser: User;
updateSelf: User;
updateUser?: Maybe<User>;
updateSelf?: Maybe<User>;
};
@ -495,7 +496,7 @@ export type ResolversTypes = {
ID: ResolverTypeWrapper<Scalars['ID']>;
String: ResolverTypeWrapper<Scalars['String']>;
ActivityFilter: ActivityFilter;
CacheControlScope: CacheControlScope;
AuthRightConfig: AuthRightConfig;
Channel: ResolverTypeWrapper<Channel>;
ChannelsQueryFilter: ChannelsQueryFilter;
CreateActivityInput: CreateActivityInput;
@ -525,7 +526,6 @@ export type ResolversTypes = {
UserActions: ResolverTypeWrapper<UserActions>;
UserActionsInput: UserActionsInput;
UserSettings: ResolverTypeWrapper<UserSettings>;
Int: ResolverTypeWrapper<Scalars['Int']>;
};
/** Mapping between all available schema types and the resolvers parents */
@ -539,6 +539,7 @@ export type ResolversParentTypes = {
ID: Scalars['ID'];
String: Scalars['String'];
ActivityFilter: ActivityFilter;
AuthRightConfig: AuthRightConfig;
Channel: Channel;
ChannelsQueryFilter: ChannelsQueryFilter;
CreateActivityInput: CreateActivityInput;
@ -568,17 +569,17 @@ export type ResolversParentTypes = {
UserActions: UserActions;
UserActionsInput: UserActionsInput;
UserSettings: UserSettings;
Int: Scalars['Int'];
};
export type CacheControlDirectiveArgs = { maxAge?: Maybe<Scalars['Int']>;
scope?: Maybe<CacheControlScope>; };
export type AuthDirectiveArgs = { roles?: Maybe<Array<Maybe<Scalars['ID']>>>;
rights?: Maybe<Array<Maybe<AuthRightConfig>>>; };
export type CacheControlDirectiveResolver<Result, Parent, ContextType = any, Args = CacheControlDirectiveArgs> = DirectiveResolverFn<Result, Parent, ContextType, Args>;
export type AuthDirectiveResolver<Result, Parent, ContextType = any, Args = AuthDirectiveArgs> = DirectiveResolverFn<Result, Parent, ContextType, Args>;
export type AccessRightsResolvers<ContextType = any, ParentType extends ResolversParentTypes['AccessRights'] = ResolversParentTypes['AccessRights']> = {
channels?: Resolver<Maybe<ResolversTypes['AccessUnit']>, ParentType, ContextType>;
streamKeys?: Resolver<Maybe<ResolversTypes['AccessUnit']>, ParentType, ContextType>;
roles?: Resolver<Maybe<ResolversTypes['AccessUnit']>, ParentType, ContextType>;
users?: Resolver<Maybe<ResolversTypes['AccessUnit']>, ParentType, ContextType>;
activities?: Resolver<Maybe<ResolversTypes['AccessUnit']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -647,8 +648,8 @@ export type MutationResolvers<ContextType = any, ParentType extends ResolversPar
revokeStreamKey?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationRevokeStreamKeyArgs, never>>;
signUp?: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<MutationSignUpArgs, 'input'>>;
signIn?: Resolver<Maybe<ResolversTypes['SessionResponse']>, ParentType, ContextType, RequireFields<MutationSignInArgs, 'input'>>;
updateUser?: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<MutationUpdateUserArgs, 'input'>>;
updateSelf?: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<MutationUpdateSelfArgs, 'input'>>;
updateUser?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<MutationUpdateUserArgs, 'input'>>;
updateSelf?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<MutationUpdateSelfArgs, 'input'>>;
};
export type QueryResolvers<ContextType = any, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
@ -762,7 +763,7 @@ export type Resolvers<ContextType = any> = {
*/
export type IResolvers<ContextType = any> = Resolvers<ContextType>;
export type DirectiveResolvers<ContextType = any> = {
cacheControl?: CacheControlDirectiveResolver<any, any, ContextType>;
auth?: AuthDirectiveResolver<any, any, ContextType>;
};

30
yarn.lock

@ -1506,6 +1506,14 @@
"@graphql-tools/utils" "^7.7.0"
tslib "~2.2.0"
"@graphql-tools/merge@^8.0.2":
version "8.0.3"
resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.0.3.tgz#56c844bc5d7d833456695c8e5eda4f1a0d5be873"
integrity sha512-lVMyW9cREs+nQYbUvMaaqSl+pRCezl2RafNMFi/04akjvOtjVefdi7n3pArpSqPhLHPJDyQRlI8CK8cmOZ9jTA==
dependencies:
"@graphql-tools/utils" "^8.1.2"
tslib "~2.3.0"
"@graphql-tools/optimize@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@graphql-tools/optimize/-/optimize-1.0.1.tgz#9933fffc5a3c63f95102b1cb6076fb16ac7bb22d"
@ -1558,6 +1566,16 @@
tslib "~2.2.0"
value-or-promise "1.0.6"
"@graphql-tools/schema@^8.1.2":
version "8.1.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.1.2.tgz#913879da1a7889a9488e9b7dc189e7c83eff74be"
integrity sha512-rX2pg42a0w7JLVYT+f/yeEKpnoZL5PpLq68TxC3iZ8slnNBNjfVfvzzOn8Q8Q6Xw3t17KP9QespmJEDfuQe4Rg==
dependencies:
"@graphql-tools/merge" "^8.0.2"
"@graphql-tools/utils" "^8.1.1"
tslib "~2.3.0"
value-or-promise "1.0.10"
"@graphql-tools/url-loader@^6", "@graphql-tools/url-loader@^6.0.0", "@graphql-tools/url-loader@^6.8.2":
version "6.10.1"
resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz#dc741e4299e0e7ddf435eba50a1f713b3e763b33"
@ -1601,6 +1619,13 @@
camel-case "4.1.2"
tslib "~2.2.0"
"@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.1.2":
version "8.1.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.1.2.tgz#a376259fafbca7532fda657e3abeec23b545e5d3"
integrity sha512-3G+NIBR5mHjPm78jAD0l07JRE0XH+lr9m7yL/wl69jAzK0Jr/H+/Ok4ljEolI70iglz+ZhIShVPAwyesF6rnFg==
dependencies:
tslib "~2.3.0"
"@graphql-tools/wrap@^7.0.4":
version "7.0.8"
resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-7.0.8.tgz#ad41e487135ca3ea1ae0ea04bb3f596177fb4f50"
@ -7461,6 +7486,11 @@ valid-url@1.0.9, valid-url@^1.0.9:
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=
value-or-promise@1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.10.tgz#5bf041f1e9a8e7043911875547636768a836e446"
integrity sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==
value-or-promise@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.6.tgz#218aa4794aa2ee24dcf48a29aba4413ed584747f"

Loading…
Cancel
Save