diff --git a/lib/engine.js b/lib/engine.js index e3736e1ba..2c569a51f 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -10,7 +10,9 @@ const crossorigin = require('cors'); const helmet = require('helmet'); const Config = require('./config'); const Storage = require('storj-service-storage-models'); -const middleware = require('storj-service-middleware'); +const { querystring } = require('./server/middleware/query-string'); +const { ErrorHandlerFactory: errorHandler } = require('./server/middleware/error-handler'); + const Server = require('./server'); const pow = require('./server/middleware/pow'); const Mailer = require('inxt-service-mailer'); @@ -26,7 +28,7 @@ const bullQueueModule = require('./core/queue/bullQueue'); const QUEUE_NAME = 'NETWORK_WORKER_TASKS_QUEUE'; -const getAllowedOrigins = ()=> { +const getAllowedOrigins = () => { const originsEnv = process.env.ALLOWED_ORIGINS; if (!originsEnv) { @@ -139,7 +141,7 @@ Engine.prototype.start = function (callback) { }); this.redis.on('error', (err) => { log.error('error connecting to redis', err); - }); + }); this.server = new Server(this._config.server, this._configureApp()); @@ -247,14 +249,14 @@ Engine.prototype._configureApp = function () { self._keepPendingResponsesClean(); app.use(requestLogger); - app.use(middleware.querystring); + app.use(querystring); app.use(this._trackResponseStatus.bind(this)); app.use(crossorigin(corsOptions)); app.use(helmet()); app.get('/', this._handleRootGET.bind(this)); routers.forEach(bindRoute); app.use(unexpectedErrorLogger); - app.use(middleware.errorhandler({ logger: log })); + app.use(errorHandler({ logger: log })); app.use(json()); diff --git a/lib/server/error-types.ts b/lib/server/error-types.ts new file mode 100644 index 000000000..7d28ff50b --- /dev/null +++ b/lib/server/error-types.ts @@ -0,0 +1,31 @@ +/** + * @module inxt-bridge/server/errors + */ + +export class HTTPError extends Error { + code: number; + statusCode: number; + + constructor(statusCode: number, message: string) { + super(message); + this.statusCode = statusCode; + this.code = statusCode; + Object.setPrototypeOf(this, new.target.prototype); + } +} + +function makeError(statusCode: number, defaultMessage: string) { + return (message?: string) => new HTTPError(statusCode, message ?? defaultMessage); +} + +export const RateLimited = makeError(429, 'Request rate limited'); +export const NotFoundError = makeError(404, 'Resource not found'); +export const NotAuthorizedError = makeError(401, 'Not authorized'); +export const ForbiddenError = makeError(403, 'Forbidden'); +export const InternalError = makeError(500, 'Internal error'); +export const BadRequestError = makeError(400, 'Bad request'); +export const NotImplementedError = makeError(501, 'Not implemented'); +export const ServiceUnavailableError = makeError(503, 'Service Unavailable'); +export const TransferRateError = makeError(420, 'Transfer rate limit'); +export const ConflictError = makeError(409, 'Conflict'); +export const UnprocessableEntityError = makeError(422, 'Unprocessable entity'); diff --git a/lib/server/http/index.ts b/lib/server/http/index.ts index 9dbf5a219..77891748c 100644 --- a/lib/server/http/index.ts +++ b/lib/server/http/index.ts @@ -33,8 +33,7 @@ import { MongoDBBucketEntryShardsRepository } from "../../core/bucketEntryShards import { Notifications } from "../notifications"; import { FileStateRepository } from "../../core/fileState/Repository"; import { MongoDBFileStateRepository } from "../../core/fileState/MongoDBFileStateRepository"; - -const { authenticate } = require('storj-service-middleware'); +import authenticateFactory from "../middleware/authenticate"; interface Models { User: any; @@ -47,6 +46,8 @@ interface Models { Shard: any; BucketEntryShard: any; FileState: any; + PublicKey: any; + UserNonce: any; } export function bindNewRoutes( @@ -109,8 +110,7 @@ export function bindNewRoutes( usersRepository, fileStateRepository ); - - const basicAuthMiddleware = authenticate(storage); + const basicAuthMiddleware = authenticateFactory({ User: storage.models.User, PublicKey: storage.models.PublicKey, UserNonce: storage.models.UserNonce }); const secretToUtf8 = Buffer.from(getEnv().gateway.jwtSecret, 'base64').toString('utf8') const jwtMiddleware = buildJwtMiddleware(secretToUtf8, { algorithms: ['RS256'] }); diff --git a/lib/server/http/middleware/jwt.ts b/lib/server/http/middleware/jwt.ts index e5565a27a..6d3a96e14 100644 --- a/lib/server/http/middleware/jwt.ts +++ b/lib/server/http/middleware/jwt.ts @@ -1,6 +1,6 @@ import { RequestHandler } from "express"; import { verify, VerifyOptions } from 'jsonwebtoken'; -import { BadRequestError, ForbiddenError, NotAuthorizedError } from "storj-service-error-types"; +import { BadRequestError, ForbiddenError, NotAuthorizedError } from '../../error-types'; export function buildMiddleware(secret: string, opts: Partial): RequestHandler { return (req, _, next) => { diff --git a/lib/server/http/users/index.ts b/lib/server/http/users/index.ts index d948c87e4..823edd3a8 100644 --- a/lib/server/http/users/index.ts +++ b/lib/server/http/users/index.ts @@ -3,7 +3,7 @@ import { HTTPUsersController } from './controller'; export const createUsersHTTPRouter = ( controller: HTTPUsersController, - basicAuth: RequestHandler + basicAuth: RequestHandler | RequestHandler[] ): Router => { const router = Router(); diff --git a/lib/server/limiter.js b/lib/server/limiter.js index cda573c70..0ec726f9a 100644 --- a/lib/server/limiter.js +++ b/lib/server/limiter.js @@ -1,7 +1,7 @@ 'use strict'; const log = require('../logger'); -const errors = require('storj-service-error-types'); +const errors = require('./error-types'); const tmpl = '{"rate_limited": {"url": "%s", "method": "%s", "ip": "%s"}}'; module.exports.DEFAULTS = (total = 1000, expire = 1000 * 60) => { diff --git a/lib/server/middleware/authenticate.js b/lib/server/middleware/authenticate.js new file mode 100644 index 000000000..fda19cc79 --- /dev/null +++ b/lib/server/middleware/authenticate.js @@ -0,0 +1,198 @@ +/** + * @module inxt-bridge/server/middleware/authenticate + */ + +'use strict'; + +const secp256k1 = require('secp256k1'); +const errors = require('../error-types'); +const url = require('url'); +const crypto = require('crypto'); +const rawBodyMiddleware = require('./raw-body'); +const basicauth = require('basic-auth'); +const fromBody = ['POST', 'PATCH', 'PUT']; +const fromQuery = ['GET', 'DELETE', 'OPTIONS']; + +function AuthenticateMiddlewareFactory({ User, PublicKey, UserNonce }) { + const models = { User, PublicKey, UserNonce }; + + function authenticate(req, res, next) { + let strategy = AuthenticateMiddlewareFactory._detectStrategy(req); + + switch (strategy) { + case 'BASIC': + AuthenticateMiddlewareFactory._basic(models, req, res, next); + break; + case 'ECDSA': + AuthenticateMiddlewareFactory._ecdsa(models, req, res, next); + break; + default: + next(errors.NotAuthorizedError( + 'No authentication strategy detected' + )); + } + } + + return [rawBodyMiddleware, authenticate]; +} + +AuthenticateMiddlewareFactory._basic = function ({ User }, req, res, next) { + let creds = basicauth(req); + User.lookup(creds.name, creds.pass, function (err, user) { + if (err) { + return next(err); + } + + if (!user.activated) { + return next(errors.ForbiddenError( + 'User account has not been activated' + )); + } + + req.user = user; + + next(); + }); +}; + +AuthenticateMiddlewareFactory._ecdsa = async function ({ User, PublicKey, UserNonce }, req, res, next) { + if (!AuthenticateMiddlewareFactory._verifySignature(req)) { + return next(errors.NotAuthorizedError('Invalid signature')); + } + + try { + const pubkey = await PublicKey.findOne({ + _id: req.header('x-pubkey') + }); + + if (!pubkey) { + return next(errors.NotAuthorizedError( + 'Public key not registered' + )); + } + + let params = AuthenticateMiddlewareFactory._getParams(req); + + const user = await User.findOne({ _id: pubkey.user }); + + if (!user) { + return next(errors.NotAuthorizedError('User not found')); + } + + if (!user.activated) { + return next(errors.ForbiddenError( + 'User account has not been activated' + )); + } + + var userNonce = new UserNonce({ + user: user.id, + nonce: params.__nonce + }); + + try { + await userNonce.save(); + req.user = user; + req.pubkey = pubkey; + + return next(); + } catch (err) { + if (err.code === 11000) { + return next(errors.NotAuthorizedError( + 'Invalid nonce supplied' + )); + } + + return next(err); + } + } catch (err) { + return next(err); + } + +}; + +/** + * Returns a string representation of the auth type detected + * @private + * @param {http.IncomingMessage} req + * @returns {String} + */ +AuthenticateMiddlewareFactory._detectStrategy = function (req) { + const creds = basicauth(req); + const basic = creds && creds.name && creds.pass; + const ecdsa = req.header('x-signature') && req.header('x-pubkey'); + + return basic ? 'BASIC' : (ecdsa ? 'ECDSA' : 'NONE'); +}; + +/** + * Extracts the payload for signature verification + * @private + * @param {http.IncomingMessage} req + * @returns {String} + */ +AuthenticateMiddlewareFactory._getPayload = function (req) { + if (fromBody.indexOf(req.method) !== -1) { + return req.rawbody; + } + + if (fromQuery.indexOf(req.method) !== -1) { + return url.parse(req.url).query; + } + + return ''; +}; + +/** + * Extracts the request parameters + * @private + * @param {http.IncomingMessage} req + * @returns {Object} + */ +AuthenticateMiddlewareFactory._getParams = function (req) { + if (fromBody.indexOf(req.method) !== -1) { + return req.body; + } + + if (fromQuery.indexOf(req.method) !== -1) { + return req.query; + } + + return {}; +}; + +AuthenticateMiddlewareFactory._getHash = function (req) { + let contract = Buffer.from([ + req.method, + req.path, + AuthenticateMiddlewareFactory._getPayload(req) + ].join('\n'), 'utf8'); + + return crypto.createHash('sha256').update(contract).digest('hex') +}; + +AuthenticateMiddlewareFactory._verifySignature = function (req) { + let signature; + let sigHeader = req.header('x-signature'); + let pubkey = req.header('x-pubkey'); + let signatureBuf; + let pubkeyBuf; + + try { + pubkeyBuf = Buffer.from(pubkey, 'hex'); + signatureBuf = Buffer.from(sigHeader, 'hex'); + signature = secp256k1.signatureImport(signatureBuf); + } catch (e) { + return false; + } + + let hash = AuthenticateMiddlewareFactory._getHash(req); + + return secp256k1.ecdsaVerify( + secp256k1.signatureNormalize(signature), + Buffer.from(hash, 'hex'), + pubkeyBuf + ); +}; + +module.exports = AuthenticateMiddlewareFactory; diff --git a/lib/server/middleware/error-handler.ts b/lib/server/middleware/error-handler.ts new file mode 100644 index 000000000..9df17453e --- /dev/null +++ b/lib/server/middleware/error-handler.ts @@ -0,0 +1,28 @@ +/** + * @module inxt-bridge/server/middleware/error-handler + */ + +import { ErrorRequestHandler } from 'express'; + +interface Logger { + error: (message: string, ...args: unknown[]) => void; +} + +function ErrorHandlerFactory(options: { logger?: Logger }): ErrorRequestHandler { + const log: Logger = options.logger ?? console; + + return function errorhandler(err, req, res, next) { + if (err) { + const statusCode = err.code ? (err.code > 500 ? 400 : err.code) : 500; + if (statusCode >= 500) { + log.error('request error: %s', err.message); + log.error(err.stack); + } + + return res.status(statusCode).send({ error: err.message }); + } + next(); + }; +} + +export { ErrorHandlerFactory }; diff --git a/lib/server/middleware/farmer-auth.js b/lib/server/middleware/farmer-auth.js deleted file mode 100644 index 6ada564c1..000000000 --- a/lib/server/middleware/farmer-auth.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -const errors = require('storj-service-error-types'); -const crypto = require('crypto'); -const secp256k1 = require('secp256k1'); - -const THRESHOLD = 300000; - -function isHexString(a) { - if (typeof a !== 'string') { - return false; - } - - return /^([0-9a-fA-F]{2})+$/.test(a); -} - -function getSigHash(req) { - const hasher = crypto.createHash('sha256'); - const timestamp = req.headers['x-node-timestamp']; - let proto = req.protocol; - if (req.headers['x-forwarded-proto']) { - proto = req.headers['x-forwarded-proto']; - } - const url = proto + '://' + req.get('host') + req.originalUrl; - hasher.update(req.method); - hasher.update(url); - hasher.update(timestamp); - hasher.update(req.rawbody); - - return hasher.digest(); -} - -function checkSig(req) { - const sighash = getSigHash(req); - let sigstr = req.headers['x-node-signature']; - if (!isHexString(sigstr)) { - return false; - } - const buf = Buffer.from(req.headers['x-node-signature'], 'hex'); - const sig = secp256k1.signatureImport(buf); - const pubkey = Buffer.from(req.headers['x-node-pubkey'], 'hex'); - - return secp256k1.ecdsaVerify(sig, sighash, pubkey); -} - -function checkPubkey(pubkey) { - if (!isHexString(pubkey)) { - return false; - } - const buf = Buffer.from(pubkey, 'hex'); - - return secp256k1.publicKeyVerify(buf); -} - -function checkTimestamp(ts) { - const timestamp = parseInt(ts); - if (!Number.isSafeInteger(timestamp)) { - return false; - } - const now = Date.now(); - if (timestamp < now - THRESHOLD || timestamp > now + THRESHOLD) { - return false; - } - - return true; -} - -function checkNodeID(nodeID, pubkey) { - if (!nodeID || nodeID.length !== 40 || !isHexString(nodeID)) { - return false; - } - const sha256 = crypto.createHash('sha256'); - const ripemd160 = crypto.createHash('ripemd160'); - sha256.update(Buffer.from(pubkey, 'hex')); - ripemd160.update(sha256.digest()); - if (ripemd160.digest('hex') !== nodeID) { - return false; - } - - return true; -} - -function authFarmer(req, res, next) { - const nodeID = req.headers['x-node-id']; - const timestamp = req.headers['x-node-timestamp']; - const pubkey = req.headers['x-node-pubkey']; - - if (!module.exports.checkTimestamp(timestamp)) { - return next(new errors.BadRequestError('Invalid timestamp header')); - } - - if (!module.exports.checkPubkey(pubkey)) { - return next(new errors.BadRequestError('Invalid pubkey header')); - } - - if (!module.exports.checkNodeID(nodeID, pubkey)) { - return next(new errors.BadRequestError('Invalid nodeID header')); - } - - if (!req.rawbody || !Buffer.isBuffer(req.rawbody)) { - return next(new errors.BadRequestError('Invalid body')); - } - - if (!module.exports.checkSig(req)) { - return next(new errors.BadRequestError('Invalid signature header')); - } - - next(); -} - -module.exports = { - authFarmer: authFarmer, - getSigHash: getSigHash, - isHexString: isHexString, - checkTimestamp: checkTimestamp, - checkNodeID: checkNodeID, - checkPubkey: checkPubkey, - checkSig: checkSig -}; diff --git a/lib/server/middleware/farmer-auth.ts b/lib/server/middleware/farmer-auth.ts new file mode 100644 index 000000000..787dce3b9 --- /dev/null +++ b/lib/server/middleware/farmer-auth.ts @@ -0,0 +1,106 @@ +"use strict"; + +import { Request, RequestHandler } from "express"; +import { BadRequestError } from "../error-types"; +import * as crypto from "crypto"; +import * as secp256k1 from "secp256k1"; + +const THRESHOLD = 300000; + +export function isHexString(a: unknown): boolean { + if (typeof a !== "string") { + return false; + } + return /^([0-9a-fA-F]{2})+$/.test(a); +} + +export function getSigHash(req: Request): Buffer { + const hasher = crypto.createHash("sha256"); + const timestamp = req.headers["x-node-timestamp"] as string; + let proto = req.protocol; + if (req.headers["x-forwarded-proto"]) { + proto = req.headers["x-forwarded-proto"] as string; + } + const url = proto + "://" + req.get("host") + req.originalUrl; + hasher.update(req.method); + hasher.update(url); + hasher.update(timestamp); + hasher.update((req as any).rawbody); + + return hasher.digest(); +} + +export function checkSig(req: Request): boolean { + const sighash = getSigHash(req); + const sigstr = req.headers["x-node-signature"] as string; + if (!isHexString(sigstr)) { + return false; + } + const buf = Uint8Array.from(Buffer.from(sigstr, "hex")); + const sig = secp256k1.signatureImport(buf); + const pubkey = Uint8Array.from(Buffer.from(req.headers["x-node-pubkey"] as string, "hex")); + + return secp256k1.ecdsaVerify(sig, sighash, pubkey); +} + +export function checkPubkey(pubkey: unknown): boolean { + if (!isHexString(pubkey)) { + return false; + } + const buf = Uint8Array.from(Buffer.from(pubkey as string, "hex")); + return secp256k1.publicKeyVerify(buf); +} + +export function checkTimestamp(ts: unknown): boolean { + const timestamp = parseInt(ts as string); + if (!Number.isSafeInteger(timestamp)) { + return false; + } + const now = Date.now(); + if (timestamp < now - THRESHOLD || timestamp > now + THRESHOLD) { + return false; + } + return true; +} + +export function checkNodeID(nodeID: unknown, pubkey: unknown): boolean { + if (!nodeID || (nodeID as string).length !== 40 || !isHexString(nodeID)) { + return false; + } + const sha256 = crypto.createHash("sha256"); + const ripemd160 = crypto.createHash("ripemd160"); + sha256.update(Uint8Array.from(Buffer.from(pubkey as string, "hex"))); + ripemd160.update(Uint8Array.from(sha256.digest())); + if (ripemd160.digest("hex") !== nodeID) { + return false; + } + return true; +} + +export const authFarmer: RequestHandler = (req, res, next) => { + const nodeID = req.headers["x-node-id"] as string; + const timestamp = req.headers["x-node-timestamp"] as string; + const pubkey = req.headers["x-node-pubkey"] as string; + + if (!checkTimestamp(timestamp)) { + return next(BadRequestError("Invalid timestamp header")); + } + + if (!checkPubkey(pubkey)) { + return next(BadRequestError("Invalid pubkey header")); + } + + if (!checkNodeID(nodeID, pubkey)) { + return next(BadRequestError("Invalid nodeID header")); + } + + if (!(req as any).rawbody || !Buffer.isBuffer((req as any).rawbody)) { + return next(BadRequestError("Invalid body")); + } + + if (!checkSig(req)) { + return next(BadRequestError("Invalid signature header")); + } + + next(); +}; diff --git a/lib/server/middleware/public-bucket.js b/lib/server/middleware/public-bucket.js new file mode 100644 index 000000000..b3938f76a --- /dev/null +++ b/lib/server/middleware/public-bucket.js @@ -0,0 +1,35 @@ +/** + * @module inxt-bridge/server/middleware/public-bucket + */ + +'use strict'; + +module.exports = function PublicBucketFactory(storage) { + const Bucket = storage.models.Bucket; + + /** + * Checks if the bucket id and operation are public + * @param {String} req.params.id - Unique bucket id + * @param {String} req.body.operation - Operation to perform + */ + return async function publicBucket(req, res, next) { + const bucketId = req.params.id; + const operation = req.body.operation || 'PULL'; + + try { + const bucket = await Bucket.findOne({ + _id: bucketId, + publicPermissions: { $in: [operation] } + }); + + if (!bucket) { + return next(new Error('Bucket not found')); + } + next(null); + } catch (err) { + return next(err); + } + + }; + +}; \ No newline at end of file diff --git a/lib/server/middleware/query-string.ts b/lib/server/middleware/query-string.ts new file mode 100644 index 000000000..f708a1609 --- /dev/null +++ b/lib/server/middleware/query-string.ts @@ -0,0 +1,12 @@ +/** + * @module inxt-bridge/server/middleware/query-string + */ +import { RequestHandler } from "express"; +import queryString from "querystring"; + +const querystring: RequestHandler = (req, res, next) => { + req.query = queryString.parse((req as any).query()); + next(); +}; + +export { querystring }; diff --git a/lib/server/middleware/rate-limiter.js b/lib/server/middleware/rate-limiter.js new file mode 100644 index 000000000..284d9aa94 --- /dev/null +++ b/lib/server/middleware/rate-limiter.js @@ -0,0 +1,89 @@ +'use strict'; + + +/** + * module - ratelimiter + * + * forked from https://github.com/juliendangers/express-limiter2 and updated + * to work with Internxt framework + * + * @param {type} db redis data client + * @param {type} app express app or express 4.0 router + * @return {type} returns a function that takes config options object + */ +module.exports = function (db, app) { + + return function (opts) { + const middleware = function (req, res, next) { + if (!db || !db.connected) { + return next(); + } + if (opts.whitelist && opts.whitelist(req)) { + return next(); + } + opts.onRateLimited = typeof opts.onRateLimited === 'function' + ? opts.onRateLimited + : function (req, res, _next) { + res.status(429).send('Too Many Requests'); + }; + // default rate-limit by IP address & method & path + opts.lookup = typeof opts.lookup === 'function' + ? opts.lookup + : function (req) { + return [req.path, req.method, req.connection.remoteAddress]; + }; + opts.keyFormatter = typeof opts.keyFormatter === 'function' + ? opts.keyFormatter + : function (parts) { + return 'rateLimit:' + parts.join(':'); + }; + const key = opts.keyFormatter(opts.lookup(req)); + db.get(key, function (err, limit) { + if (err && opts.ignoreErrors) { + return next(); + } + const now = Date.now(); + limit = limit + ? JSON.parse(limit) + : { + total: opts.total, + remaining: opts.total, + reset: now + opts.expire + }; + + if (now > limit.reset) { + limit.reset = now + opts.expire; + limit.remaining = opts.total; + } + + // do not allow negative remaining + limit.remaining = Math.max(Number(limit.remaining) - 1, -1); + db.set(key, JSON.stringify(limit), 'PX', opts.expire, function () { + if (!opts.skipHeaders) { + res.set('X-RateLimit-Limit', limit.total); + res.set('X-RateLimit-Remaining', Math.max(limit.remaining, 0)); + res.set('X-RateLimit-Reset', Math.ceil(limit.reset / 1000)); // UTC epoch seconds + } + + if (limit.remaining >= 0) { + return next(); + } + + const after = (limit.reset - Date.now()) / 1000; + + if (!opts.skipHeaders) { + res.set('Retry-After', after); + } + + opts.onRateLimited(req, res, next); + }); + + }); + }; + if (app && opts.method && opts.path) { + app[opts.method](opts.path, middleware); + } + + return middleware; + }; +}; diff --git a/lib/server/middleware/token-auth.js b/lib/server/middleware/token-auth.js new file mode 100644 index 000000000..92b618afa --- /dev/null +++ b/lib/server/middleware/token-auth.js @@ -0,0 +1,23 @@ +/** + * @module inxt-bridge/server/middleware/authenticate + */ + +'use strict'; + +function TokenMiddlewareFactory(storage) { + const Token = storage.models.Token; + + return function tokenauth(req, res, next) { + Token.lookup(req.header('x-token'), function (err, token) { + if (err) { + return next(err); + } + + req.token = token; + + token.expire(next); + }); + }; +} + +module.exports = TokenMiddlewareFactory; diff --git a/lib/server/routes/buckets.js b/lib/server/routes/buckets.js index 947339b79..d50d6b4a4 100644 --- a/lib/server/routes/buckets.js +++ b/lib/server/routes/buckets.js @@ -4,18 +4,18 @@ const ms = require('ms'); const assert = require('assert'); const async = require('async'); const storj = require('storj-lib'); -const middleware = require('storj-service-middleware'); -const authenticate = middleware.authenticate; -const tokenauth = middleware.tokenauth; -const publicBucket = middleware.publicBucket; +const authenticate = require('../middleware/authenticate'); +const tokenauth = require('../middleware/token-auth'); +const publicBucket = require('../middleware/public-bucket'); const log = require('../../logger'); const merge = require('merge'); -const errors = require('storj-service-error-types'); +const errors = require('../error-types'); const Router = require('./index'); const inherits = require('util').inherits; const utils = require('../../utils'); const constants = require('../../constants'); const limiter = require('../limiter').DEFAULTS; +const rateLimiter = require('../middleware/rate-limiter'); const { randomBytes } = require('crypto'); const DELETING_FILE_MESSAGE = require('../queues/messageTypes').DELETING_FILE_MESSAGE; const { v4: uuidv4, validate: uuidValidate } = require('uuid'); @@ -51,10 +51,10 @@ function BucketsRouter(options) { Router.apply(this, arguments); - this._verify = authenticate(this.storage); + this._verify = authenticate({ User: this.storage.models.User, PublicKey: this.storage.models.PublicKey, UserNonce: this.storage.models.UserNonce }); this._isPublic = publicBucket(this.storage); this._usetoken = tokenauth(this.storage); - this.getLimiter = middleware.rateLimiter(options.redis); + this.getLimiter = rateLimiter(options.redis); this.CLUSTER = Object.values(options.config.application.CLUSTER || []); this.networkQueue = options.networkQueue; @@ -939,7 +939,7 @@ BucketsRouter.prototype.getFiles = async function (req, res, next) { const fileIdList = req.query.fileIds.split(','); const bucketId = req.params.id; - const list = await this.storage.models.BucketEntry.find({ _id: { $in: fileIdList }, bucket: bucketId }).populate({ path: 'frame', populate:{ path: 'shards' } }).exec(); + const list = await this.storage.models.BucketEntry.find({ _id: { $in: fileIdList }, bucket: bucketId }).populate({ path: 'frame', populate: { path: 'shards' } }).exec(); const v2Files = list.filter(f => f.version && f.version === 2); const v1Files = list.filter(f => !f.version || f.version === 1); @@ -1080,7 +1080,7 @@ BucketsRouter.prototype.deletePointers = async function (pointers) { } } catch (err) { console.error('deletePointers: Failed to enqueue BullMQ job for pointer shard %s: %s', hash, err.message); - } + } } pointer.deleteOne().catch((err) => { @@ -1091,7 +1091,7 @@ BucketsRouter.prototype.deletePointers = async function (pointers) { // eslint-disable-next-line complexity BucketsRouter.prototype.getFileInfo = async function (req, res, next) { - const getBucketUnregisteredAsync = (req, res)=> { + const getBucketUnregisteredAsync = (req, res) => { return new Promise((resolve, reject) => { this._getBucketUnregistered(req, res, (err, bucket) => { if (err) { @@ -1130,7 +1130,7 @@ BucketsRouter.prototype.getFileInfo = async function (req, res, next) { } await this.fileStateUsecase.updateOrSetLastAccessDate(req.params.file) - .catch(error=> + .catch(error => log.error('getFileInfo: Error updating last access date for user %s and file %s : %s', req.user.uuid, req.params.file, error.message) ); @@ -1302,7 +1302,7 @@ BucketsRouter.prototype.finishUpload = async function (req, res, next) { ); await this.fileStateUsecase.updateOrSetLastAccessDate(bucketEntry.id) - .catch(error=> + .catch(error => log.error('finishUpload: Error inserting last access date for user %s and file %s : %s', req.user.uuid, bucketEntry.id, error.message) ); @@ -1353,7 +1353,7 @@ BucketsRouter.prototype.finishUpload = async function (req, res, next) { }; BucketsRouter.prototype.getDownloadLinks = async function (req, res, next) { - const { id:bucketId, file:fileId } = req.params; + const { id: bucketId, file: fileId } = req.params; const { BucketEntry, BucketEntryShard, Shard, Mirror } = this.storage.models; if (!bucketId) { diff --git a/lib/server/routes/contacts.js b/lib/server/routes/contacts.js index d6f160e08..a4b1e57a3 100644 --- a/lib/server/routes/contacts.js +++ b/lib/server/routes/contacts.js @@ -3,11 +3,11 @@ const Router = require('./index'); const dns = require('dns'); const isIp = require('is-ip'); -const errors = require('storj-service-error-types'); +const errors = require('../error-types'); const inherits = require('util').inherits; -const middleware = require('storj-service-middleware'); const log = require('../../logger'); const limiter = require('../limiter').DEFAULTS; +const rateLimiter = require('../middleware/rate-limiter'); const rawBody = require('../middleware/raw-body'); const { getPOWMiddleware, getChallenge } = require('../middleware/pow'); const { authFarmer } = require('../middleware/farmer-auth'); @@ -26,7 +26,7 @@ function ContactsRouter(options) { this.redis = options.redis; this.checkPOW = getPOWMiddleware(options.redis); - this.getLimiter = middleware.rateLimiter(options.redis); + this.getLimiter = rateLimiter(options.redis); this.options = options; } diff --git a/lib/server/routes/frames.js b/lib/server/routes/frames.js index 242a506c3..ab6cd2d89 100644 --- a/lib/server/routes/frames.js +++ b/lib/server/routes/frames.js @@ -1,11 +1,11 @@ 'use strict'; -const middleware = require('storj-service-middleware'); -const authenticate = middleware.authenticate; -const errors = require('storj-service-error-types'); +const authenticate = require('../middleware/authenticate'); +const errors = require('../error-types'); const Router = require('./index'); const inherits = require('util').inherits; const limiter = require('../limiter').DEFAULTS; +const rateLimiter = require('../middleware/rate-limiter'); const log = require('../../logger'); /** @@ -20,8 +20,8 @@ function FramesRouter(options) { Router.apply(this, arguments); this._defaults = options.config.application; - this._verify = authenticate(this.storage); - this.getLimiter = middleware.rateLimiter(options.redis); + this._verify = authenticate({ User: this.storage.models.User, PublicKey: this.storage.models.PublicKey, UserNonce: this.storage.models.UserNonce }); + this.getLimiter = rateLimiter(options.redis); this.CLUSTER = Object.values(options.config.application.CLUSTER || []); } diff --git a/lib/server/routes/gateway.js b/lib/server/routes/gateway.js index 15a92ef39..0ce8cdf41 100644 --- a/lib/server/routes/gateway.js +++ b/lib/server/routes/gateway.js @@ -1,10 +1,9 @@ const Router = require('./index'); -const errors = require('storj-service-error-types'); -const middleware = require('storj-service-middleware'); +const errors = require('../error-types'); const jwt = require('jsonwebtoken'); const analytics = require('../../analytics'); const logger = require('../../logger'); -const rawbody = middleware.rawbody; +const rawbody = require('../middleware/raw-body'); class GatewayRouter extends Router { constructor(options) { diff --git a/lib/server/routes/stripe.js b/lib/server/routes/stripe.js index c4852df96..b4b29a08a 100644 --- a/lib/server/routes/stripe.js +++ b/lib/server/routes/stripe.js @@ -2,11 +2,11 @@ const Router = require('./index'); const inherits = require('util').inherits; -const middleware = require('storj-service-middleware'); -const authenticate = middleware.authenticate; -const rawbody = middleware.rawbody; +const authenticate = require('../middleware/authenticate'); +const rawbody = require('../middleware/raw-body'); const limiter = require('../limiter').DEFAULTS; -const errors = require('storj-service-error-types'); +const rateLimiter = require('../middleware/rate-limiter'); +const errors = require('../error-types'); const log = require('../../logger'); const Stripe = require('stripe'); const axios = require('axios'); @@ -26,8 +26,8 @@ function StripeRouter(options) { Router.apply(this, arguments); - this._verify = authenticate(this.storage); - this.getLimiter = middleware.rateLimiter(options.redis); + this._verify = authenticate({ User: this.storage.models.User, PublicKey: this.storage.models.PublicKey, UserNonce: this.storage.models.UserNonce }); + this.getLimiter = rateLimiter(options.redis); } inherits(StripeRouter, Router); diff --git a/lib/server/routes/users.js b/lib/server/routes/users.js index 8551ef284..cec673fed 100644 --- a/lib/server/routes/users.js +++ b/lib/server/routes/users.js @@ -1,16 +1,16 @@ 'use strict'; const Router = require('./index'); -const middleware = require('storj-service-middleware'); -const rawbody = middleware.rawbody; +const rawbody = require('../middleware/raw-body'); const log = require('../../logger'); -const errors = require('storj-service-error-types'); +const errors = require('../error-types'); const merge = require('merge'); const inherits = require('util').inherits; -const authenticate = middleware.authenticate; +const authenticate = require('../middleware/authenticate'); const crypto = require('crypto'); const storj = require('storj-lib'); const limiter = require('../limiter').DEFAULTS; +const rateLimiter = require('../middleware/rate-limiter'); const disposable = require('disposable-email'); const { trackUserActivated } = require('../../analytics'); const url = require('url'); @@ -28,8 +28,8 @@ function UsersRouter(options) { Router.apply(this, arguments); - this._verify = authenticate(this.storage); - this.getLimiter = middleware.rateLimiter(options.redis); + this._verify = authenticate({ User: this.storage.models.User, PublicKey: this.storage.models.PublicKey, UserNonce: this.storage.models.UserNonce }); + this.getLimiter = rateLimiter(options.redis); } inherits(UsersRouter, Router); diff --git a/package.json b/package.json index 51025a973..2977116fa 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "analytics-node": "^2.4.0", "async": "^3.2.0", "axios": "0.30.2", + "basic-auth": "^2.0.1", "bn.js": "^4.11.8", "bullmq": "^5.70.1", "commander": "8.3.0", @@ -122,8 +123,6 @@ "semver": "^7.7.4", "storj-lib": "github:internxt/core#v8.7.3-beta", "storj-mongodb-adapter": "github:internxt/mongodb-adapter#10.1.0-beta", - "storj-service-error-types": "github:internxt/service-error-types", - "storj-service-middleware": "github:internxt/service-middleware", "storj-service-storage-models": "github:internxt/service-storage-models#11.1.0", "stripe": "^8.49.0", "through": "^2.3.8", diff --git a/yarn.lock b/yarn.lock index f986a65f9..a3de66d67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3075,23 +3075,6 @@ concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -concat@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/concat/-/concat-1.0.3.tgz#40f3353089d65467695cb1886b45edd637d8cca8" - integrity sha512-f/ZaH1aLe64qHgTILdldbvyfGiGF4uzeo9IuXUloIOLQzFmIPloy9QbZadNsuVv0j5qbKQvQb/H/UYf2UsKTpw== - dependencies: - commander "^2.9.0" - console-browserify@1.1.x: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -7616,11 +7599,6 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== -querystring@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" - integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== - querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -7720,7 +7698,7 @@ readable-stream@^2.0.1, readable-stream@^2.0.4, readable-stream@^2.0.5, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -8586,18 +8564,6 @@ stop-iteration-iterator@^1.1.0: version "1.5.0" resolved "https://codeload.github.com/internxt/service-error-types/tar.gz/29a0c2e8a439ab7b9ff3c110a3eadf16932b3a1c" -"storj-service-middleware@github:internxt/service-middleware": - version "1.3.3" - resolved "https://codeload.github.com/internxt/service-middleware/tar.gz/176acea1a2bdacd3bc83710e566940780e247d97" - dependencies: - basic-auth "^2.0.1" - concat "^1.0.3" - concat-stream "^2.0.0" - querystring "^0.2.1" - redis "^3.1.0" - secp256k1 "^4.0.0" - storj-service-error-types "github:internxt/service-error-types" - "storj-service-storage-models@github:internxt/service-storage-models#11.1.0": version "11.0.1" resolved "https://codeload.github.com/internxt/service-storage-models/tar.gz/cdfcd9b6fd844f7d454e8f58de2c5763ec3f9fa2"