Skip to content
Snippets Groups Projects
Commit 6972f755 authored by Eric Frias's avatar Eric Frias
Browse files

Merge branch 'hiveauth-support' into 'master'

Changes to make it work with HiveAuth

See merge request !4
parents 9368369a 19f40762
No related branches found
No related tags found
1 merge request!4Changes to make it work with HiveAuth
......@@ -32,7 +32,9 @@ test: node_modules
.PHONY: ci-test
ci-test: node_modules reports
yarn audit
# Disabling yarn audit for now because all packages are too old
# @TODO update and test packages
# yarn audit
tslint -p tsconfig.json -c tslint.json
NODE_ENV=test nyc -r lcov -e .ts -i ts-node/register \
--report-dir reports/coverage \
......
......@@ -7,11 +7,15 @@ service_url = 'SERVICE_URL'
default_avatar = 'DEFAULT_AVATAR'
redis_url = 'REDIS_URL'
rpc_node = 'RPC_NODE'
[upload_store]
type = 'UPLOAD_STORAGE_TYPE'
fs_store_path = 'UPLOAD_STORAGE_FS_PATH'
s3_bucket = 'UPLOAD_S3_BUCKET'
[proxy_store]
type = 'PROXY_STORAGE_TYPE'
fs_store_path = 'UPLOAD_PROXY_FS_PATH'
s3_bucket = 'PROXY_S3_BUCKET'
max_image_width = 'PROXY_MAX_IMAGE_WIDTH'
max_image_height = 'PROXY_MAX_IMAGE_HEIGHT'
......
......@@ -12,14 +12,14 @@ name = 'imagehoster'
# number of worker processes to spawn, 0 = autodetect
num_workers = 1
# url to steemd node used for verifying signatures
rpc_node = 'https://api.steemit.com'
# url to hived node used for verifying signatures
rpc_node = 'https://api.hive.blog'
# url where service is running
service_url = 'http://localhost:8800'
# default user avatar, should be a png minimum 512x512
default_avatar = 'https://steemitimages.com/DQmb2HNSGKN3pakguJ4ChCRjgkVuDN9WniFRPmrxoJ4sjR4'
default_avatar = 'https://images.hive.blog/DQmb2HNSGKN3pakguJ4ChCRjgkVuDN9WniFRPmrxoJ4sjR4'
# log level to output at
log_level = 'debug'
......
......@@ -2,8 +2,13 @@ num_workers = 0
port = 3234
proxy = true
log_level = 'info'
rpc_node = 'https://api.steemit.com'
rpc_node = 'https://api.hive.blog'
service_url = 'http://localhost:3234'
[upload_store]
type = 's3'
type = 'fs'
fs_store_path = '/tmp'
s3_bucket = ''
[proxy_store]
type = 's3'
type = 'fs'
fs_store_path = '/tmp'
s3_bucket = ''
\ No newline at end of file
log_level = 'fatal'
service_url = 'http://localhost:63205'
port = 63205
rpc_node = 'https://api.hive.blog'
\ No newline at end of file
version: "3.7"
services:
imagehoster:
build:
context: .
container_name: imagehoster
ports:
- 8800:8800
- 3234:3234
# volumes:
# - ./yarn.lock:/app/yarn.lock:delegated
# - ./package.json:/app/package.json:delegated
# - ./Makefile:/app/Makefile:delegated
# - ./test:/app/test:delegated
# - ./src:/app/src:delegated
# - ./config:/app/config:delegated
# - ./.git:/app/.git:delegated
# - ./tsconfig.json:/app/tsconfig.json:delegated
# - ./tslint.json:/app/tslint.json:delegated
......@@ -16,6 +16,7 @@
"bunyan": "^2.0.2",
"busboy": "^0.2.14",
"config": "^1.29.4",
"fs-blob-store": "^6.0.0",
"hivesigner": "^3.2.0",
"koa": "^2.3.0",
"koa-router": "^7.4.0",
......@@ -49,8 +50,8 @@
"mocha-junit-reporter": "^1.17.0",
"nyc": "^14.1.1",
"onchange": "^6.1.0",
"ts-node": "^7.0.0",
"ts-node": "^8.10.2",
"tslint": "^5.16.0",
"typescript": "^2.7.1"
"typescript": "^3.9.6"
}
}
/** Misc shared instances. */
import {Client} from '@hiveio/dhive'
import {AbstractBlobStore} from 'abstract-blob-store'
import * as config from 'config'
import {Client} from '@hiveio/dhive'
import {IRouterContext} from 'koa-router'
import * as Redis from 'redis'
......@@ -33,7 +33,10 @@ if (config.has('redis_url')) {
let S3Client: any
function loadStore(key: string): AbstractBlobStore {
const conf = config.get(key) as any
if (conf.type === 'memory') {
if (conf.type === 'fs') {
logger.warn('using file store for %s', key)
return require('fs-blob-store')('/tmp')
} else if (conf.type === 'memory') {
logger.warn('using memory store for %s', key)
return require('abstract-blob-store')()
} else if (conf.type === 's3') {
......
......@@ -7,7 +7,7 @@ import {KoaContext} from './common'
import {legacyProxyHandler} from './legacy-proxy'
import {proxyHandler} from './proxy'
import {serveHandler} from './serve'
import {uploadHandler, uploadHsHandler} from './upload'
import {uploadCsHandler, uploadHandler, uploadHsHandler} from './upload'
const version = require('./version')
const router = new Router()
......@@ -24,6 +24,7 @@ router.get('/.well-known/healthcheck.json', healthcheck as any)
router.get('/u/:username/avatar/:size?', avatarHandler as any)
router.post('/hs/:accesstoken', uploadHsHandler as any)
router.post('/:username/:signature', uploadHandler as any)
router.post('/cs/:username/:signature', uploadCsHandler as any)
router.get('/:width(\\d+)x:height(\\d+)/:url(.*)', legacyProxyHandler as any)
router.get('/p/:url', proxyHandler as any)
router.get('/:hash/:filename?', serveHandler as any)
......
/** Uploads file to blob store. */
import {Client, Signature} from '@hiveio/dhive'
import * as Busboy from 'busboy'
import * as config from 'config'
import {createHash} from 'crypto'
import {Client, Signature} from '@hiveio/dhive'
// @ts-ignore
import * as hivesigner from 'hivesigner'
import * as http from 'http'
import * as multihash from 'multihashes'
import * as RateLimiter from 'ratelimiter'
import {URL} from 'url'
import * as hivesigner from 'hivesigner'
import {accountBlacklist} from './blacklist'
import {KoaContext, redisClient, rpcClient, uploadStore} from './common'
......@@ -78,13 +79,15 @@ async function getRatelimit(account: string) {
})
})
}
const b64uLookup = {
'/': '_', _: '/', '+': '-', '-': '+', '=': '.', '.': '=',
const b64uLookup: Record<string, string> = {
'/': '_', '_': '/', '+': '-', '-': '+', '=': '.', '.': '=',
}
function b64uToB64 (str: string) {
const tt = str.replace(/(-|_|\.)/g, function(m) { return b64uLookup[m]})
function b64uToB64(str: string) {
const tt = str.replace(/(-|_|\.)/g, (m) => b64uLookup[m])
return tt
}
/** Handling upload with HiveSigner */
export async function uploadHsHandler(ctx: KoaContext) {
ctx.tag({handler: 'hsupload'})
let validSignature = false
......@@ -112,7 +115,7 @@ export async function uploadHsHandler(ctx: KoaContext) {
.update('ImageSigningChallenge')
.update(data)
.digest()
const token = ctx.params['accesstoken']
const decoded = Buffer.from(b64uToB64(token), 'base64').toString()
const tokenObj = JSON.parse(decoded)
......@@ -140,7 +143,7 @@ export async function uploadHsHandler(ctx: KoaContext) {
accessToken: token,
})
await cl.me(function (err: any, res: any) {
await cl.me((err: any, res: any) => {
if (!err && res) {
account = res.account
APIError.assert(account, APIError.Code.NoSuchAccount)
......@@ -152,18 +155,20 @@ export async function uploadHsHandler(ctx: KoaContext) {
if (account && account.name) {
['posting', 'active', 'owner'].forEach((type) => {
account[type].account_auths.forEach((key: string[]) => {
// @ts-ignore
// tslint:disable-next-line:no-shadowed-variable
account[type].account_auths.forEach((key: string[]) => {
if (
!validSignature
&& key[0] === UPLOAD_LIMITS.app_account
) {
validSignature = true;
validSignature = true
}
});
});
})
})
}
}
});
})
APIError.assert(validSignature, APIError.Code.InvalidSignature)
APIError.assert(!accountBlacklist.includes(account.name), APIError.Code.Blacklisted)
......@@ -194,6 +199,104 @@ export async function uploadHsHandler(ctx: KoaContext) {
ctx.body = {url}
}
}
/** Handling upload by signing image checksum */
export async function uploadCsHandler(ctx: KoaContext) {
ctx.tag({handler: 'upload'})
APIError.assert(ctx.method === 'POST', {code: APIError.Code.InvalidMethod})
APIError.assertParams(ctx.params, ['username', 'signature'])
let signature: Signature
try {
signature = Signature.fromString(ctx.params['signature'])
} catch (cause) {
throw new APIError({code: APIError.Code.InvalidSignature, cause})
}
APIError.assert(ctx.get('content-type').includes('multipart/form-data'),
{message: 'Only multipart uploads are supported'})
const contentLength = Number.parseInt(ctx.get('content-length'))
APIError.assert(Number.isFinite(contentLength),
APIError.Code.LengthRequired)
APIError.assert(contentLength <= MAX_IMAGE_SIZE,
APIError.Code.PayloadTooLarge)
const file = await parseMultipart(ctx.req)
const fileData = await readStream(file.stream)
const fileHash = createHash('sha256')
.update(fileData)
.digest()
// extra check if client manges to lie about the content-length
APIError.assert((file.stream as any).truncated !== true,
APIError.Code.PayloadTooLarge)
const imageHash = createHash('sha256')
.update('ImageSigningChallenge')
.update(fileHash)
.digest()
const [account] = await rpcClient.database.getAccounts([ctx.params['username']])
APIError.assert(account, APIError.Code.NoSuchAccount)
let validSignature = false
let publicKey
try {
publicKey = signature.recover(imageHash).toString()
} catch (cause) {
throw new APIError({code: APIError.Code.InvalidSignature, cause})
}
const thresholdPosting = account.posting.weight_threshold
for (const auth of account.posting.key_auths) {
if (auth[0] === publicKey && auth[1] >= thresholdPosting) {
validSignature = true
break
}
}
const thresholdActive = account.active.weight_threshold
for (const auth of account.active.key_auths) {
if (auth[0] === publicKey && auth[1] >= thresholdActive) {
validSignature = true
break
}
}
APIError.assert(validSignature, APIError.Code.InvalidSignature)
APIError.assert(!accountBlacklist.includes(account.name), APIError.Code.Blacklisted)
let limit: RateLimit = {total: 0, remaining: Infinity, reset: 0}
try {
limit = await getRatelimit(account.name)
} catch (error) {
ctx.log.warn(error, 'unable to enforce upload rate limits')
}
APIError.assert(limit.remaining > 0, APIError.Code.QoutaExceeded)
APIError.assert(repLog10(account.reputation) >= UPLOAD_LIMITS.reputation, APIError.Code.Deplorable)
const key = 'D' + multihash.toB58String(multihash.encode(imageHash, 'sha2-256'))
const url = new URL(`${ key }/${ file.name }`, SERVICE_URL)
if (!(await storeExists(uploadStore, key))) {
await storeWrite(uploadStore, key, fileData)
} else {
ctx.log.debug('key %s already exists in store', key)
}
ctx.log.info({uploader: account.name, size: fileData.byteLength}, 'image uploaded')
ctx.status = 200
ctx.body = {url}
}
/** Handling upload by signing image data */
export async function uploadHandler(ctx: KoaContext) {
ctx.tag({handler: 'upload'})
......@@ -217,7 +320,6 @@ export async function uploadHandler(ctx: KoaContext) {
APIError.assert(contentLength <= MAX_IMAGE_SIZE,
APIError.Code.PayloadTooLarge)
const file = await parseMultipart(ctx.req)
const data = await readStream(file.stream)
......@@ -236,11 +338,10 @@ export async function uploadHandler(ctx: KoaContext) {
let validSignature = false
let publicKey
try {
publicKey = signature.recover(imageHash).toString()
publicKey = signature.recover(imageHash).toString()
} catch (cause) {
throw new APIError({code: APIError.Code.InvalidSignature, cause})
}
const thresholdPosting = account.posting.weight_threshold
for (const auth of account.posting.key_auths) {
if (auth[0] === publicKey && auth[1] >= thresholdPosting) {
......
......@@ -46,7 +46,7 @@ before(() => {
_client.call = async (api: string, method: string, params = []) => {
const apiMethod = `${ api }-${ method }`
switch (apiMethod) {
case 'database_api-get_accounts':
case 'condenser_api-get_accounts':
assert.equal(params.length, 1, 'can only mock single account lookups')
return [mockAccounts[params[0]]]
default:
......
import 'mocha'
import * as assert from 'assert'
import * as crypto from 'crypto'
import * as fs from 'fs'
import * as http from 'http'
import 'mocha'
import * as needle from 'needle'
import * as path from 'path'
import * as fs from 'fs'
import * as crypto from 'crypto'
import {PrivateKey} from '@hiveio/dhive'
import {app} from './../src/app'
import {rpcClient} from './../src/common'
import {testKeys} from './index'
......@@ -27,17 +25,23 @@ export async function uploadImage(data: Buffer, port: number) {
},
}
const signature = testKeys.foo.sign(hash).toString()
needle.post(`:${ port }/foo/${ signature }`, payload, {multipart: true}, function (error, response, body) {
if (error) {
reject(error)
} else {
resolve({response, body})
}
})
const url = `http://localhost:${ port }/foo/${ signature }`
needle.post(
url,
payload,
{multipart: true},
(error, response, body) => {
if (error) {
reject(error)
} else {
resolve({response, body})
}
},
)
})
}
describe('upload', function() {
describe('upload', () => {
const port = 63205
const server = http.createServer(app.callback())
......
......@@ -406,6 +406,11 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
......@@ -413,11 +418,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
arrify@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
arrify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
......@@ -545,7 +545,7 @@ bs58@^4.0.1:
dependencies:
base-x "^3.0.2"
buffer-from@^1.0.0, buffer-from@^1.1.0:
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
......@@ -968,7 +968,7 @@ dicer@0.2.5:
readable-stream "1.1.x"
streamsearch "0.1.2"
diff@3.5.0, diff@^3.1.0:
diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
......@@ -999,6 +999,16 @@ dtrace-provider@~0.8:
dependencies:
nan "^2.14.0"
duplexify@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0"
integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==
dependencies:
end-of-stream "^1.4.1"
inherits "^2.0.3"
readable-stream "^3.1.1"
stream-shift "^1.0.0"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
......@@ -1027,7 +1037,7 @@ encodeurl@^1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
......@@ -1145,6 +1155,16 @@ from2@^2.0.3:
inherits "^2.0.1"
readable-stream "^2.0.0"
fs-blob-store@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/fs-blob-store/-/fs-blob-store-6.0.0.tgz#30b467f6f26d2a1d68720a9cede224605972ff14"
integrity sha512-oMdboCaw6kTRXi5lKfjpw7DO7maC+gwFmaef3DsYQTYHtOoTmCo13ZGH1GoqFaD81RW5qbaT9YKlP+OA4dzbdA==
dependencies:
duplexify "^4.1.1"
end-of-stream "^1.4.4"
lru-cache "^6.0.0"
mkdirp-classic "^0.5.3"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
......@@ -1672,6 +1692,13 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
......@@ -1779,7 +1806,7 @@ minizlib@^1.2.1:
dependencies:
minipass "^2.9.0"
mkdirp-classic@^0.5.2:
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
......@@ -2481,10 +2508,10 @@ simple-swizzle@^0.2.2:
dependencies:
is-arrayish "^0.3.1"
source-map-support@^0.5.6:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
source-map-support@^0.5.17:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
......@@ -2554,6 +2581,11 @@ stream-head@^1.1.0:
dependencies:
through2 "2.x"
stream-shift@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
streamsearch@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
......@@ -2742,19 +2774,16 @@ tree-kill@^1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
ts-node@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf"
integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==
ts-node@^8.10.2:
version "8.10.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
dependencies:
arrify "^1.0.0"
buffer-from "^1.1.0"
diff "^3.1.0"
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
minimist "^1.2.0"
mkdirp "^0.5.1"
source-map-support "^0.5.6"
yn "^2.0.0"
source-map-support "^0.5.17"
yn "3.1.1"
tslib@^1.8.0, tslib@^1.8.1:
version "1.13.0"
......@@ -2812,10 +2841,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^2.7.1:
version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
typescript@^3.9.6:
version "3.9.10"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
urijs@^1.19.0:
version "1.19.2"
......@@ -2969,6 +2998,11 @@ yallist@^3.0.0, yallist@^3.0.3:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^13.0.0, yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
......@@ -2998,7 +3032,7 @@ ylru@^1.2.0:
resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"
integrity sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==
yn@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"
integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment