Skip to content
Snippets Groups Projects
Commit 9654eabe authored by feruzm's avatar feruzm
Browse files

upload image with hivesigner access token

parent 869c0543
No related branches found
No related tags found
1 merge request!1Upload images with Hivesigner access token
...@@ -146,12 +146,12 @@ Creating a signature (psuedocode): ...@@ -146,12 +146,12 @@ Creating a signature (psuedocode):
signature = secp256k1_sign(sha256('ImageSigningChallenge'+image_data), account_private_posting_key) signature = secp256k1_sign(sha256('ImageSigningChallenge'+image_data), account_private_posting_key)
``` ```
Creating a signature (node.js & [dsteem](https://github.com/jnordberg/dsteem)) Creating a signature (node.js & [dhive](https://github.com/openhive-network/dhive))
```js ```js
#!/usr/bin/env node #!/usr/bin/env node
const dsteem = require('dsteem') const dhive = require('@hiveio/dhive')
const crypto = require('crypto') const crypto = require('crypto')
const fs = require('fs') const fs = require('fs')
...@@ -163,7 +163,7 @@ if (!wif || !file) { ...@@ -163,7 +163,7 @@ if (!wif || !file) {
} }
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
const key = dsteem.PrivateKey.fromString(wif) const key = dhive.PrivateKey.fromString(wif)
const imageHash = crypto.createHash('sha256') const imageHash = crypto.createHash('sha256')
.update('ImageSigningChallenge') .update('ImageSigningChallenge')
.update(data) .update(data)
......
...@@ -38,6 +38,7 @@ max_image_size = 10000000 # 10mb ...@@ -38,6 +38,7 @@ max_image_size = 10000000 # 10mb
duration = 604800000 # in ms (=1 week) duration = 604800000 # in ms (=1 week)
max = 300 # max requests within duration max = 300 # max requests within duration
reputation = 10 # minimum reputation needed for upload reputation = 10 # minimum reputation needed for upload
app_account = 'hive.blog'
# blob stores, valid types are: memory, s3 # blob stores, valid types are: memory, s3
# the s3 type additionally needs the s3_bucket key to be set and expects # the s3 type additionally needs the s3_bucket key to be set and expects
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
"bunyan": "^2.0.2", "bunyan": "^2.0.2",
"busboy": "^0.2.14", "busboy": "^0.2.14",
"config": "^1.29.4", "config": "^1.29.4",
"dsteem": "^0.9.0",
"koa": "^2.3.0", "koa": "^2.3.0",
"koa-router": "^7.4.0", "koa-router": "^7.4.0",
"mmmagic": "^0.5.0", "mmmagic": "^0.5.0",
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import * as config from 'config' import * as config from 'config'
import { base58Enc } from './utils' import { base58Enc } from './utils'
import { Account } from 'dsteem' import { Account } from '@hiveio/dhive'
import {KoaContext, rpcClient} from './common' import {KoaContext, rpcClient} from './common'
import {APIError} from './error' import {APIError} from './error'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import {AbstractBlobStore} from 'abstract-blob-store' import {AbstractBlobStore} from 'abstract-blob-store'
import * as config from 'config' import * as config from 'config'
import {Client} from 'dsteem' import {Client} from '@hiveio/dhive'
import {IRouterContext} from 'koa-router' import {IRouterContext} from 'koa-router'
import * as Redis from 'redis' import * as Redis from 'redis'
......
...@@ -7,7 +7,7 @@ import {KoaContext} from './common' ...@@ -7,7 +7,7 @@ import {KoaContext} from './common'
import {legacyProxyHandler} from './legacy-proxy' import {legacyProxyHandler} from './legacy-proxy'
import {proxyHandler} from './proxy' import {proxyHandler} from './proxy'
import {serveHandler} from './serve' import {serveHandler} from './serve'
import {uploadHandler} from './upload' import {uploadHandler, uploadHsHandler} from './upload'
const version = require('./version') const version = require('./version')
const router = new Router() const router = new Router()
...@@ -22,6 +22,7 @@ async function healthcheck(ctx: KoaContext) { ...@@ -22,6 +22,7 @@ async function healthcheck(ctx: KoaContext) {
router.get('/', healthcheck as any) router.get('/', healthcheck as any)
router.get('/.well-known/healthcheck.json', healthcheck as any) router.get('/.well-known/healthcheck.json', healthcheck as any)
router.get('/u/:username/avatar/:size?', avatarHandler 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('/:username/:signature', uploadHandler as any)
router.get('/:width(\\d+)x:height(\\d+)/:url(.*)', legacyProxyHandler as any) router.get('/:width(\\d+)x:height(\\d+)/:url(.*)', legacyProxyHandler as any)
router.get('/p/:url', proxyHandler as any) router.get('/p/:url', proxyHandler as any)
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
import * as Busboy from 'busboy' import * as Busboy from 'busboy'
import * as config from 'config' import * as config from 'config'
import {createHash} from 'crypto' import {createHash} from 'crypto'
import {Client, Signature} from 'dsteem' import {Client, Signature} from '@hiveio/dhive'
import * as http from 'http' import * as http from 'http'
import * as multihash from 'multihashes' import * as multihash from 'multihashes'
import * as RateLimiter from 'ratelimiter' import * as RateLimiter from 'ratelimiter'
import {URL} from 'url' import {URL} from 'url'
import * as hivesigner from 'hivesigner'
import {accountBlacklist} from './blacklist' import {accountBlacklist} from './blacklist'
import {KoaContext, redisClient, rpcClient, uploadStore} from './common' import {KoaContext, redisClient, rpcClient, uploadStore} from './common'
...@@ -77,7 +78,122 @@ async function getRatelimit(account: string) { ...@@ -77,7 +78,122 @@ async function getRatelimit(account: string) {
}) })
}) })
} }
const b64uLookup = {
'/': '_', _: '/', '+': '-', '-': '+', '=': '.', '.': '=',
}
function b64uToB64 (str: string) {
const tt = str.replace(/(-|_|\.)/g, function(m) { return b64uLookup[m]})
return tt
}
export async function uploadHsHandler(ctx: KoaContext) {
ctx.tag({handler: 'hsupload'})
let validSignature = false
APIError.assert(ctx.method === 'POST', {code: APIError.Code.InvalidMethod})
APIError.assertParams(ctx.params, ['accesstoken'])
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 data = await readStream(file.stream)
// 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(data)
.digest()
const token = ctx.params['accesstoken']
const decoded = Buffer.from(b64uToB64(token), 'base64').toString()
const tokenObj = JSON.parse(decoded)
const signedMessage = tokenObj.signed_message
if (
tokenObj.authors
&& tokenObj.authors[0]
&& tokenObj.signatures
&& tokenObj.signatures[0]
&& signedMessage
&& signedMessage.type
&& ['login', 'posting', 'offline', 'code', 'refresh']
.includes(signedMessage.type)
&& signedMessage.app
) {
const username = tokenObj.authors[0]
let account = {
name: '',
reputation: 0,
}
const cl = new hivesigner.Client({
app: UPLOAD_LIMITS.app_account,
accessToken: token,
})
await cl.me(function (err: any, res: any) {
if (!err && res) {
account = res.account
APIError.assert(account, APIError.Code.NoSuchAccount)
ctx.log.warn('uploading app %s', signedMessage.app)
APIError.assert(username === account.name, APIError.Code.InvalidSignature)
APIError.assert(signedMessage.app === UPLOAD_LIMITS.app_account, APIError.Code.InvalidSignature)
APIError.assert(res.scope.includes('comment'), APIError.Code.InvalidSignature)
if (account && account.name) {
['posting', 'active', 'owner'].forEach((type) => {
account[type].account_auths.forEach((key: string[]) => {
if (
!validSignature
&& key[0] === UPLOAD_LIMITS.app_account
) {
validSignature = true;
}
});
});
}
}
});
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, data)
} else {
ctx.log.debug('key %s already exists in store', key)
}
ctx.log.info({uploader: account.name, size: data.byteLength}, 'image uploaded')
ctx.status = 200
ctx.body = {url}
}
}
export async function uploadHandler(ctx: KoaContext) { export async function uploadHandler(ctx: KoaContext) {
ctx.tag({handler: 'upload'}) ctx.tag({handler: 'upload'})
......
import 'mocha' import 'mocha'
import * as assert from 'assert' import * as assert from 'assert'
import {PrivateKey} from 'dsteem' import {PrivateKey} from '@hiveio/dhive'
import {rpcClient} from './../src/common' import {rpcClient} from './../src/common'
...@@ -41,7 +41,7 @@ export const mockAccounts: any = { ...@@ -41,7 +41,7 @@ export const mockAccounts: any = {
} }
before(() => { before(() => {
// mock out dsteem rpc calls // mock out dhive rpc calls
const _client = rpcClient as any const _client = rpcClient as any
_client.call = async (api: string, method: string, params = []) => { _client.call = async (api: string, method: string, params = []) => {
const apiMethod = `${ api }-${ method }` const apiMethod = `${ api }-${ method }`
......
...@@ -5,7 +5,7 @@ import * as needle from 'needle' ...@@ -5,7 +5,7 @@ import * as needle from 'needle'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import {PrivateKey} from 'dsteem' import {PrivateKey} from '@hiveio/dhive'
import {app} from './../src/app' import {app} from './../src/app'
import {rpcClient} from './../src/common' import {rpcClient} from './../src/common'
......
This diff is collapsed.
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