Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
}
}, {
"enforceForRenamedProperties": false
}]
}],
"object-curly-newline": 0
}
}
2 changes: 1 addition & 1 deletion .mdeprc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"node": "10.12.0",
"node": "10.14.1",
"rebuild": ["ms-flakeless", "scrypt"]
}
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test:e2e:cluster": "mdep test run",
"test:e2e:sentinel": "mdep test run --docker_compose ./test/docker-compose.sentinel.yml",
"test:e2e": "mkdir -p ./ss && yarn test:e2e:cluster && yarn test:e2e:sentinel",
"lint": "eslint ./src",
"lint": "eslint ./src ./test",
"prepublishOnly": "yarn compile",
"semantic-release": "semantic-release",
"docker-release": "mdep docker release",
Expand All @@ -31,30 +31,30 @@
"@microfleet/core": "^13.3.1",
"@microfleet/transport-amqp": "^13.1.2",
"@microfleet/validation": "^8.1.0",
"bell": "^9.3.1",
"bell": "^9.4.0",
"bluebird": "^3.5.3",
"bunyan": "^1.8.12",
"bytes": "^3.0.0",
"common-errors": "^1.0.5",
"csv-write-stream": "^2.0.0",
"disposable-email-domains": "^1.0.40",
"disposable-email-domains": "^1.0.41",
"dlock": "^8.1.0",
"flake-idgen": "^1.1.0",
"get-stdin": "^6.0.0",
"get-value": "^3.0.1",
"handlebars": "^4.0.12",
"hapi": "^17.7.0",
"ioredis": "^4.2.0",
"ioredis": "^4.3.0",
"is": "^3.2.1",
"jsonwebtoken": "^8.3.0",
"jsonwebtoken": "^8.4.0",
"jwa": "^1.1.6",
"lodash": "^4.17.11",
"moment": "^2.22.2",
"ms-conf": "^3.3.1",
"ms-flakeless": "^4.1.0",
"ms-mailer-client": "^8.0.1",
"ms-mailer-templates": "^1.10.0",
"ms-token": "^2.0.0",
"ms-token": "^3.0.0",
"otplib": "^10.0.1",
"password-generator": "^2.2.0",
"redis-filtered-sort": "^2.3.0",
Expand All @@ -67,28 +67,28 @@
"tough-cookie": "^2.4.3",
"uuid": "^3.3.2",
"vision": "^5.4.3",
"yargs": "^12.0.2"
"yargs": "^12.0.4"
},
"devDependencies": {
"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.5",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-transform-strict-mode": "^7.0.0",
"@babel/cli": "^7.2.0",
"@babel/core": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.1",
"@babel/plugin-proposal-object-rest-spread": "^7.2.0",
"@babel/plugin-transform-strict-mode": "^7.2.0",
"@babel/register": "^7.0.0",
"@makeomatic/deploy": "^8.2.3",
"@semantic-release/changelog": "^3.0.1",
"@semantic-release/exec": "^3.3.0",
"@semantic-release/git": "^7.0.5",
"apidoc": "^0.17.6",
"apidoc": "^0.17.7",
"apidoc-plugin-schema": "^0.1.8",
"babel-eslint": "^10.0.1",
"babel-plugin-istanbul": "^5.1.0",
"chai": "^4.2.0",
"cheerio": "^1.0.0-rc.2",
"codecov": "^3.1.0",
"cross-env": "^5.2.0",
"eslint": "^5.8.0",
"eslint": "^5.10.0",
"eslint-config-makeomatic": "^3.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-mocha": "^5.2.0",
Expand All @@ -101,7 +101,7 @@
"nyc": "^13.1.0",
"puppeteer": "1.4.0",
"rimraf": "^2.6.1",
"sinon": "^7.1.1"
"sinon": "^7.2.0"
},
"engines": {
"node": ">= 8.9.0",
Expand Down
28 changes: 25 additions & 3 deletions schemas/login.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
},
"password": {
"type": "string",
"minLength": 1,
"maxLength": 50
"minLength": 1
},
"audience": {
"type": "string",
Expand All @@ -31,6 +30,29 @@
"isSSO": {
"default": false,
"type": "boolean"
},
"isOAuthFollowUp": {
"default": false,
"type": "boolean"
}
},
"if": {
"required": ["isOAuthFollowUp"],
"properties": { "isOAuthFollowUp": { "const": true } }
},
"then": {
"required": ["password"],
"properties": {
"password": {
"maxLength": 8192
}
}
},
"else": {
"properties": {
"password": {
"maxLength": 50
}
}
}
}
}
10 changes: 10 additions & 0 deletions scripts/dropLoginCounter.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local remote = KEYS[1]
local globalRemote = KEYS[2]

-- current attempts
local attempts = redis.call("GET", remote) or 0

-- delete remote
-- reduce amount of failed sign ins for that ip address
redis.call("DEL", remote)
redis.call("INCRBY", globalRemote, -attempts)
4 changes: 2 additions & 2 deletions src/actions/activate.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ function RethrowForbidden(error) {
function verifyToken(args, opts) {
const { tokenManager } = this;

return tokenManager
.verify(args, opts)
return Promise
.resolve(tokenManager.verify(args, opts))
.bind(this)
.catch(RethrowForbidden)
.get('id');
Expand Down
22 changes: 14 additions & 8 deletions src/actions/invite-remove.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { ActionTransport } = require('@microfleet/core');
const { HttpStatusError } = require('common-errors');
const {
INVITATIONS_INDEX,
Expand All @@ -15,16 +16,21 @@ const {
* @apiParam (Payload) {Object} id - id of the invitation
*
*/
module.exports = function removeInvite(request) {
module.exports = async function removeInvite({ params }) {
const { redis, tokenManager } = this;
const { id } = request.params;
const { id } = params;

return tokenManager
.remove({ id, action: USERS_ACTION_INVITE })
.tap(() => redis.srem(INVITATIONS_INDEX, id))
.catch({ message: 404 }, () => {
try {
const response = await tokenManager.remove({ id, action: USERS_ACTION_INVITE });
await redis.srem(INVITATIONS_INDEX, id);
return response;
} catch (e) {
if (e.message === '404') {
throw new HttpStatusError(404, `Invite with id "${id}" not found`);
});
}

throw e;
}
};

module.exports.transports = [require('@microfleet/core').ActionTransport.amqp];
module.exports.transports = [ActionTransport.amqp];
43 changes: 31 additions & 12 deletions src/actions/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ const isActive = require('../utils/isActive');
const isBanned = require('../utils/isBanned');
const handlePipeline = require('../utils/pipelineError');
const { checkMFA } = require('../utils/mfa');
const { verifySignedToken } = require('../auth/oauth/utils/getSignedToken');
const {
USERS_ACTION_DISPOSABLE_PASSWORD,
USERS_DISPOSABLE_PASSWORD_MIA,
USERS_ID_FIELD,
USERS_USERNAME_FIELD,
USERS_MFA_FLAG,
MFA_TYPE_OPTIONAL,
USERS_INVALID_TOKEN,
ErrorUserNotFound,
} = require('../constants');

Expand Down Expand Up @@ -94,18 +96,33 @@ function verifyHash({ password }, comparableInput) {
return scrypt.verify(password, comparableInput);
}

async function verifyOAuthToken({ id }, token) {
const providerData = await verifySignedToken.call(this, token);

if (providerData.profile.userId !== id) {
throw USERS_INVALID_TOKEN;
}

return true;
}

/**
* Checks onу-time password
*/
function verifyDisposablePassword(ctx, data) {
return ctx
.tokenManager
.verify({
async function verifyDisposablePassword(ctx, data) {
try {
return await ctx.tokenManager.verify({
action: USERS_ACTION_DISPOSABLE_PASSWORD,
id: data[USERS_USERNAME_FIELD],
token: ctx.password,
})
.catchThrow(is404, USERS_DISPOSABLE_PASSWORD_MIA);
});
} catch (e) {
if (is404(e)) {
throw USERS_DISPOSABLE_PASSWORD_MIA;
}

throw e;
}
}

/**
Expand All @@ -124,6 +141,10 @@ function getVerifyStrategy(data) {
return verifyDisposablePassword(this, data);
}

if (this.isOAuthFollowUp === true) {
return verifyOAuthToken.call(this.service, data, this.password);
}

return verifyHash(data, this.password);
}

Expand All @@ -132,13 +153,10 @@ function getVerifyStrategy(data) {
*/
function dropLoginCounter() {
this.loginAttempts = 0;
this.globalLoginAttempts = 0;

return this.redis
.pipeline()
.del(this.remoteipKey)
.incrby(this.globalRemoteIpKey, -1)
.exec()
.then(handlePipeline);
.dropLoginCounter(2, this.remoteipKey, this.globalRemoteIpKey);
}

/**
Expand Down Expand Up @@ -190,7 +208,7 @@ function verifyInternalData(data) {
function login({ params, locals }) {
const config = this.config.jwt;
const { redis, tokenManager } = this;
const { isDisposablePassword, isSSO, password } = params;
const { isOAuthFollowUp, isDisposablePassword, isSSO, password } = params;
const { lockAfterAttempts, globalLockAfterAttempts, defaultAudience } = config;
const audience = params.audience || defaultAudience;
const remoteip = params.remoteip || false;
Expand All @@ -210,6 +228,7 @@ function login({ params, locals }) {

// business logic params
params,
isOAuthFollowUp,
isDisposablePassword,
isSSO,
password,
Expand Down
40 changes: 22 additions & 18 deletions src/actions/oauth/facebook.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const { ActionTransport } = require('@microfleet/core');
const Promise = require('bluebird');
const Errors = require('common-errors');

const { ERROR_AUTH_REQUIRED } = require('../../constants');
const attach = require('../../auth/oauth/utils/attach');
const getSignedToken = require('../../auth/oauth/utils/getSignedToken');
const { getSignedToken } = require('../../auth/oauth/utils/getSignedToken');

function facebookCallbackAction(request) {
async function facebookCallbackAction(request) {
const { credentials } = request.auth;
const { user, account } = credentials;

Expand Down Expand Up @@ -35,32 +35,36 @@ function facebookCallbackAction(request) {
const facebook = {
uid,
email,
profile,
provider,
internals,
profile: {
...profile,
},
};

return Promise
const context = await Promise
.bind(this, [facebook, user])
.spread(user ? attach : getSignedToken)
.then(context => ({
payload: context,
error: false,
missingPermissions,
type: 'ms-users:attached',
title: `Attached ${provider} account`,
}));
.spread(user ? attach : getSignedToken);

return {
payload: context,
error: false,
missingPermissions,
type: 'ms-users:attached',
title: `Attached ${provider} account`,
};
}

facebookCallbackAction.allowed = function isAllowed(request) {
async function isAllowed(request) {
if (!request.auth.credentials) {
throw new Errors.HttpStatusError(401, 'authentication required');
throw ERROR_AUTH_REQUIRED;
}
};
}

facebookCallbackAction.allowed = isAllowed;
facebookCallbackAction.auth = 'oauth';
facebookCallbackAction.strategy = 'facebook';
facebookCallbackAction.passAuthError = true;
facebookCallbackAction.transports = [require('@microfleet/core').ActionTransport.http];
facebookCallbackAction.transports = [ActionTransport.http];

module.exports = facebookCallbackAction;
Loading