first ongoing draft of community-authentication

This commit is contained in:
Claus-Peter Huebner 2023-10-20 01:43:29 +02:00
parent 53b2fe33a0
commit 841979a360
21 changed files with 554 additions and 3 deletions

View File

@ -0,0 +1,42 @@
import { Community as DbCommunity } from '@entity/Community'
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
import { CONFIG } from '@/config'
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
import { backendLogger as logger } from '@/server/logger'
import { OpenConnectionArgs } from './client/1_0/model/OpenConnectionArgs'
import { AuthenticationClientFactory } from './client/AuthenticationClientFactory'
export async function startCommunityAuthentication(
foreignFedCom: DbFederatedCommunity,
): Promise<void> {
const homeCom = await DbCommunity.findOneByOrFail({ foreign: false })
const homeFedCom = await DbFederatedCommunity.findOneByOrFail({
foreign: false,
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
})
const foreignCom = await DbCommunity.findOneByOrFail({ publicKey: foreignFedCom.publicKey })
if (foreignCom && foreignCom.communityUuid === null && foreignCom.authenticatedAt === null) {
try {
const client = AuthenticationClientFactory.getInstance(homeFedCom)
// eslint-disable-next-line camelcase
if (client instanceof V1_0_AuthenticationClient) {
const args = new OpenConnectionArgs()
args.publicKey = homeCom.publicKey.toString('hex')
// TODO encrypt url with foreignCom.publicKey and sign it with homeCom.privateKey
args.url = homeFedCom.endPoint.endsWith('/')
? homeFedCom.endPoint
: homeFedCom.endPoint + '/' + homeFedCom.apiVersion
if (await client.openConnection(args)) {
logger.info(`Authentication: successful initiated at community:`, foreignFedCom.endPoint)
} else {
logger.error(`Authentication: can't initiate at community:`, foreignFedCom.endPoint)
}
}
} catch (err) {
logger.error(`Error:`, err)
}
}
}

View File

@ -0,0 +1,50 @@
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
import { GraphQLClient } from 'graphql-request'
import { backendLogger as logger } from '@/server/logger'
import { OpenConnectionArgs } from './model/OpenConnectionArgs'
import { openConnection } from './query/openConnection'
export class AuthenticationClient {
dbCom: DbFederatedCommunity
endpoint: string
client: GraphQLClient
constructor(dbCom: DbFederatedCommunity) {
this.dbCom = dbCom
this.endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${
dbCom.apiVersion
}/`
this.client = new GraphQLClient(this.endpoint, {
method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
}
async openConnection(args: OpenConnectionArgs): Promise<boolean | undefined> {
logger.debug('Authentication: openConnection with endpoint', this.endpoint)
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(openConnection, { args })
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!data?.openConnection) {
logger.warn(
'Authentication: openConnection without response data from endpoint',
this.endpoint,
)
return false
}
logger.debug(
'Authentication: openConnection successfully started with endpoint',
this.endpoint,
)
return true
} catch (err) {
logger.error('Authentication: error on openConnection', err)
}
}
}

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class OpenConnectionArgs {
@Field(() => String)
publicKey: string
@Field(() => String)
url: string
}

View File

@ -0,0 +1,7 @@
import { gql } from 'graphql-request'
export const openConnection = gql`
mutation ($args: OpenConnectionArgs!) {
openConnection(data: $args)
}
`

View File

@ -0,0 +1,5 @@
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
// eslint-disable-next-line camelcase
export class AuthenticationClient extends V1_0_AuthenticationClient {}

View File

@ -0,0 +1,62 @@
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_1_AuthenticationClient } from '@/federation/client/1_1/AuthenticationClient'
import { ApiVersionType } from '@/federation/enum/apiVersionType'
// eslint-disable-next-line camelcase
type AuthenticationClient = V1_0_AuthenticationClient | V1_1_AuthenticationClient
interface AuthenticationClientInstance {
id: number
// eslint-disable-next-line no-use-before-define
client: AuthenticationClient
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AuthenticationClientFactory {
private static instanceArray: AuthenticationClientInstance[] = []
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
private constructor() {}
private static createAuthenticationClient = (dbCom: DbFederatedCommunity) => {
switch (dbCom.apiVersion) {
case ApiVersionType.V1_0:
return new V1_0_AuthenticationClient(dbCom)
case ApiVersionType.V1_1:
return new V1_1_AuthenticationClient(dbCom)
default:
return null
}
}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(dbCom: DbFederatedCommunity): AuthenticationClient | null {
const instance = AuthenticationClientFactory.instanceArray.find(
(instance) => instance.id === dbCom.id,
)
if (instance) {
return instance.client
}
const client = AuthenticationClientFactory.createAuthenticationClient(dbCom)
if (client) {
AuthenticationClientFactory.instanceArray.push({
id: dbCom.id,
client,
} as AuthenticationClientInstance)
}
return client
}
}

View File

@ -10,6 +10,7 @@ import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommuni
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
import { backendLogger as logger } from '@/server/logger'
import { startCommunityAuthentication } from './authenticateCommunities'
import { ApiVersionType } from './enum/apiVersionType'
export async function startValidateCommunities(timerInterval: number): Promise<void> {
@ -40,7 +41,11 @@ export async function validateCommunities(): Promise<void> {
const apiValueStrings: string[] = Object.values(ApiVersionType)
logger.debug(`suppported ApiVersions=`, apiValueStrings)
if (!apiValueStrings.includes(dbCom.apiVersion)) {
logger.warn('Federation: dbCom with unsupported apiVersion', dbCom.endPoint, dbCom.apiVersion)
logger.debug(
'Federation: dbCom with unsupported apiVersion',
dbCom.endPoint,
dbCom.apiVersion,
)
continue
}
try {
@ -54,7 +59,8 @@ export async function validateCommunities(): Promise<void> {
const pubComInfo = await client.getPublicCommunityInfo()
if (pubComInfo) {
await writeForeignCommunity(dbCom, pubComInfo)
logger.info(`Federation: write publicInfo of community: name=${pubComInfo.name}`)
void startCommunityAuthentication(dbCom)
logger.debug(`Federation: write publicInfo of community: name=${pubComInfo.name}`)
} else {
logger.warn('Federation: missing result of getPublicCommunityInfo')
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

View File

@ -16,7 +16,6 @@
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
},
"dependencies": {
"@types/uuid": "8.3.4",
"apollo-server-express": "^2.25.2",
"await-semaphore": "0.1.3",
"class-validator": "^0.13.2",
@ -26,9 +25,11 @@
"dotenv": "10.0.0",
"express": "4.17.1",
"graphql": "15.5.1",
"graphql-request": "^5.0.0",
"lodash.clonedeep": "^4.5.0",
"log4js": "^6.7.1",
"reflect-metadata": "^0.1.13",
"sodium-native": "^4.0.4",
"type-graphql": "^1.1.1",
"uuid": "8.3.2"
},
@ -37,6 +38,8 @@
"@types/jest": "27.0.2",
"@types/lodash.clonedeep": "^4.5.6",
"@types/node": "^16.10.3",
"@types/sodium-native": "^2.3.7",
"@types/uuid": "8.3.4",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"apollo-server-testing": "2.25.2",

View File

@ -0,0 +1,70 @@
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
import { GraphQLClient } from 'graphql-request'
import { federationLogger as logger } from '@/server/logger'
import { OpenConnectionCallbackArgs } from '@/graphql/api/1_0/model/OpenConnectionCallbackArgs'
import { openConnectionCallback } from './query/openConnectionCallback'
import { AuthenticationArgs } from '@/graphql/api/1_0/model/AuthenticationArgs'
import { authenticate } from './query/authenticate'
export class AuthenticationClient {
dbCom: DbFederatedCommunity
endpoint: string
client: GraphQLClient
constructor(dbCom: DbFederatedCommunity) {
this.dbCom = dbCom
this.endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${
dbCom.apiVersion
}/`
this.client = new GraphQLClient(this.endpoint, {
method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
}
async openConnectionCallback(args: OpenConnectionCallbackArgs): Promise<boolean | undefined> {
logger.debug('Authentication: openConnectionCallback with endpoint', this.endpoint, args)
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(openConnectionCallback, { args })
if (!data?.openConnectionCallback) {
logger.warn(
'Authentication: openConnectionCallback without response data from endpoint',
this.endpoint,
)
return false
}
logger.debug(
'Authentication: openConnectionCallback successfully started with endpoint',
this.endpoint,
)
return true
} catch (err) {
logger.error('Authentication: error on openConnectionCallback', err)
}
}
async authenticate(args: AuthenticationArgs): Promise<string | undefined> {
logger.debug('Authentication: authenticate with endpoint=', this.endpoint)
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(authenticate, {})
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!data?.authenticate) {
logger.warn(
'Authentication: authenticate without response data from endpoint',
this.endpoint,
)
return
}
const
} catch (err) {
logger.error('Authentication: authenticate failed for endpoint', this.endpoint)
}
}
}

View File

@ -0,0 +1,7 @@
import { gql } from 'graphql-request'
export const authenticate = gql`
mutation ($args: AuthenticateArgs!) {
authenticate(data: $args)
}
`

View File

@ -0,0 +1,7 @@
import { gql } from 'graphql-request'
export const openConnectionCallback = gql`
mutation ($args: OpenConnectionCallbackArgs!) {
openConnectionCallback(data: $args)
}
`

View File

@ -0,0 +1,5 @@
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_0_AuthenticationClient } from '../1_0/AuthenticationClient'
// eslint-disable-next-line camelcase
export class AuthenticationClient extends V1_0_AuthenticationClient {}

View File

@ -0,0 +1,61 @@
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_0_AuthenticationClient } from './1_0/AuthenticationClient'
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_1_AuthenticationClient } from './1_1/AuthenticationClient'
import { ApiVersionType } from './enum/apiVersionType'
// eslint-disable-next-line camelcase
type AuthenticationClient = V1_0_AuthenticationClient | V1_1_AuthenticationClient
interface AuthenticationClientInstance {
id: number
// eslint-disable-next-line no-use-before-define
client: AuthenticationClient
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AuthenticationClientFactory {
private static instanceArray: AuthenticationClientInstance[] = []
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
private constructor() {}
private static createAuthenticationClient = (dbCom: DbFederatedCommunity) => {
switch (dbCom.apiVersion) {
case ApiVersionType.V1_0:
return new V1_0_AuthenticationClient(dbCom)
case ApiVersionType.V1_1:
return new V1_1_AuthenticationClient(dbCom)
default:
return null
}
}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(dbCom: DbFederatedCommunity): AuthenticationClient | null {
const instance = AuthenticationClientFactory.instanceArray.find(
(instance) => instance.id === dbCom.id,
)
if (instance) {
return instance.client
}
const client = AuthenticationClientFactory.createAuthenticationClient(dbCom)
if (client) {
AuthenticationClientFactory.instanceArray.push({
id: dbCom.id,
client,
} as AuthenticationClientInstance)
}
return client
}
}

View File

@ -0,0 +1,4 @@
export enum ApiVersionType {
V1_0 = '1_0',
V1_1 = '1_1',
}

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class AuthenticationArgs {
@Field(() => String)
oneTimeCode: string
@Field(() => String)
uuid: string
}

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class OpenConnectionArgs {
@Field(() => String)
publicKey: string
@Field(() => String)
url: string
}

View File

@ -0,0 +1,13 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class OpenConnectionCallbackArgs {
@Field(() => String)
oneTimeCode: string
@Field(() => String)
publicKey: string
@Field(() => String)
url: string
}

View File

@ -0,0 +1,51 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Arg, Mutation, Resolver } from 'type-graphql'
import { federationLogger as logger } from '@/server/logger'
import { Community as DbCommunity } from '@entity/Community'
import { LogError } from '@/server/LogError'
import { OpenConnectionArgs } from '../model/OpenConnectionArgs'
import {
startOpenConnectionCallback,
startOpenConnectionRedirect,
} from '../util/authenticateCommunity'
import { OpenConnectionCallbackArgs } from '../model/OpenConnectionCallbackArgs'
import { ApiVersionType } from '@/client/enum/apiVersionType'
@Resolver()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class AuthenticationResolver {
@Mutation(() => Boolean)
async openConnection(
@Arg('data')
args: OpenConnectionArgs,
): Promise<boolean> {
logger.debug(`Authentication: openConnection() via apiVersion=1_0 ...`, args)
// first find with args.publicKey the community, which starts openConnection request
const requestedCom = await DbCommunity.findOneBy({
publicKey: Buffer.from(args.publicKey),
})
if (!requestedCom) {
throw new LogError(`unknown requesting community with publicKey`, args.publicKey)
}
void startOpenConnectionRedirect(args, requestedCom, ApiVersionType.V1_0)
return true
}
@Mutation(() => Boolean)
async openConnectionCallback(
@Arg('data')
args: OpenConnectionCallbackArgs,
): Promise<boolean> {
logger.debug(`Authentication: openConnectionCallback() via apiVersion=1_0 ...`, args)
// first find with args.publicKey the community, which invokes openConnectionCallback
const callbackCom = await DbCommunity.findOneBy({
publicKey: Buffer.from(args.publicKey),
})
if (!callbackCom) {
throw new LogError(`unknown callback community with publicKey`, args.publicKey)
}
void startOpenConnectionCallback(args, callbackCom)
return true
}
}

View File

@ -0,0 +1,75 @@
import { OpenConnectionArgs } from '../model/OpenConnectionArgs'
import { Community as DbCommunity } from '@entity/Community'
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
import { federationLogger as logger } from '@/server/logger'
import { OpenConnectionCallbackArgs } from '../model/OpenConnectionCallbackArgs'
// eslint-disable-next-line camelcase
import { randombytes_random } from 'sodium-native'
import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory'
import { ApiVersionType } from '@/client/enum/apiVersionType'
// eslint-disable-next-line camelcase
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/client/1_0/AuthenticationClient'
export async function startOpenConnectionRedirect(
args: OpenConnectionArgs,
requestedCom: DbCommunity,
api: ApiVersionType,
): Promise<void> {
logger.debug(
`Authentication: startOpenConnectionRedirect()...`,
args.publicKey,
args.url,
requestedCom,
)
try {
// TODO verify signing of args.url with requestedCom.publicKey and decrypt with homeCom.privateKey
const homeCom = await DbCommunity.findOneByOrFail({ foreign: false })
const homeFedCom = await DbFederatedCommunity.findOneByOrFail({
foreign: false,
apiVersion: api,
})
const oneTimeCode = randombytes_random()
// store oneTimeCode in requestedCom.community_uuid for authenticate-request-identifier
requestedCom.communityUuid = oneTimeCode.toString()
await DbCommunity.save(requestedCom)
const client = AuthenticationClientFactory.getInstance(homeFedCom)
// eslint-disable-next-line camelcase
if (client instanceof V1_0_AuthenticationClient) {
const callbackArgs = new OpenConnectionCallbackArgs()
callbackArgs.oneTimeCode = oneTimeCode.toString()
callbackArgs.publicKey = homeCom.publicKey.toString('hex')
// TODO signing of callbackArgs.url with requestedCom.publicKey and decrypt with homeCom.privateKey
callbackArgs.url = homeFedCom.endPoint.endsWith('/')
? homeFedCom.endPoint
: homeFedCom.endPoint + '/' + homeFedCom.apiVersion
if (await client.openConnectionCallback(callbackArgs)) {
logger.debug('Authentication: startOpenConnectionRedirect() successful:', callbackArgs)
} else {
logger.error('Authentication: startOpenConnectionRedirect() failed:', callbackArgs)
}
}
} catch (err) {
logger.error('Authentication: error in startOpenConnectionRedirect:', err)
}
}
export async function startOpenConnectionCallback(
args: OpenConnectionCallbackArgs,
callbackCom: DbCommunity,
): Promise<void> {
logger.debug(
`Authentication: startOpenConnectionCallback()...`,
args.publicKey,
args.url,
callbackCom,
)
try {
// TODO verify signing of args.url with requestedCom.publicKey and decrypt with homeCom.privateKey
const homeCom = await DbCommunity.findOneByOrFail({ foreign: false })
} catch (err) {
logger.error('Authentication: error in startOpenConnectionCallback:', err)
}
}

View File

@ -374,6 +374,11 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6"
integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==
"@graphql-typed-document-node/core@^3.1.1":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
"@humanwhocodes/config-array@^0.11.10":
version "0.11.10"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2"
@ -1052,6 +1057,13 @@
"@types/mime" "*"
"@types/node" "*"
"@types/sodium-native@^2.3.7":
version "2.3.7"
resolved "https://registry.yarnpkg.com/@types/sodium-native/-/sodium-native-2.3.7.tgz#fdcbd026e9a730e574e69ccb85fd36fd50220a8c"
integrity sha512-VlwblVfVHizegm0QJX0Hgna+w7P9z5Gy+LYkO7EWlOj7tew2kj1csq8ziGMiruL+dm/WjRwaoGuE6STV+0bN2g==
dependencies:
"@types/node" "*"
"@types/stack-utils@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
@ -1948,6 +1960,13 @@ cross-env@^7.0.3:
dependencies:
cross-spawn "^7.0.1"
cross-fetch@^3.1.5:
version "3.1.8"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
dependencies:
node-fetch "^2.6.12"
cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -2678,6 +2697,11 @@ express@^4.17.1:
utils-merge "1.0.1"
vary "~1.1.2"
extract-files@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a"
integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -3017,6 +3041,16 @@ graphql-query-complexity@^0.7.0:
dependencies:
lodash.get "^4.4.2"
graphql-request@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.2.0.tgz#a05fb54a517d91bb2d7aefa17ade4523dc5ebdca"
integrity sha512-pLhKIvnMyBERL0dtFI3medKqWOz/RhHdcgbZ+hMMIb32mEPa5MJSzS4AuXxfI4sRAu6JVVk5tvXuGfCWl9JYWQ==
dependencies:
"@graphql-typed-document-node/core" "^3.1.1"
cross-fetch "^3.1.5"
extract-files "^9.0.0"
form-data "^3.0.0"
graphql-subscriptions@^1.0.0, graphql-subscriptions@^1.1.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz#2142b2d729661ddf967b7388f7cf1dd4cf2e061d"
@ -4228,6 +4262,18 @@ node-fetch@^2.6.1:
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.12:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
node-gyp-build@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.1.tgz#24b6d075e5e391b8d5539d98c7fc5c210cac8a3e"
integrity sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -4943,6 +4989,13 @@ slash@^4.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
sodium-native@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-4.0.4.tgz#561b7c39c97789f8202d6fd224845fe2e8cd6879"
integrity sha512-faqOKw4WQKK7r/ybn6Lqo1F9+L5T6NlBJJYvpxbZPetpWylUVqz449mvlwIBKBqxEHbWakWuOlUt8J3Qpc4sWw==
dependencies:
node-gyp-build "^4.6.0"
source-map-support@^0.5.6:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"