mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-01-04 09:05:49 -05:00
committed by
GitHub
parent
335044fece
commit
9b6042e44d
@@ -162,6 +162,8 @@ export default function initializeUser(User) {
|
||||
User.definition.properties.rand.default = getRandomNumber;
|
||||
// increase user accessToken ttl to 900 days
|
||||
User.settings.ttl = 900 * 24 * 60 * 60 * 1000;
|
||||
// Sets ttl to 900 days for mobile login created access tokens
|
||||
User.settings.maxTTL = 900 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// username should not be in blocklist
|
||||
User.validatesExclusionOf('username', {
|
||||
@@ -341,6 +343,21 @@ export default function initializeUser(User) {
|
||||
);
|
||||
};
|
||||
|
||||
User.prototype.mobileLoginByRequest = function mobileLoginByRequest(
|
||||
req,
|
||||
res
|
||||
) {
|
||||
return new Promise((resolve, reject) =>
|
||||
this.createAccessToken({}, (err, accessToken) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
setAccessTokenToResponse({ accessToken }, req, res);
|
||||
return resolve(accessToken);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
User.afterRemote('logout', function ({ req, res }, result, next) {
|
||||
removeCookies(req, res);
|
||||
next();
|
||||
|
||||
@@ -2,10 +2,9 @@ import dedent from 'dedent';
|
||||
import { check } from 'express-validator';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import passport from 'passport';
|
||||
import fetch from 'node-fetch';
|
||||
import { isEmail } from 'validator';
|
||||
|
||||
import { jwtSecret } from '../../../../config/secrets';
|
||||
|
||||
import { decodeEmail } from '../../common/utils';
|
||||
import {
|
||||
createPassportCallbackAuthenticator,
|
||||
@@ -14,7 +13,11 @@ import {
|
||||
} from '../component-passport';
|
||||
import { wrapHandledError } from '../utils/create-handled-error.js';
|
||||
import { removeCookies } from '../utils/getSetAccessToken';
|
||||
import { ifUserRedirectTo, ifNoUserRedirectHome } from '../utils/middleware';
|
||||
import {
|
||||
ifUserRedirectTo,
|
||||
ifNoUserRedirectHome,
|
||||
ifNotMobileRedirect
|
||||
} from '../utils/middleware';
|
||||
import { getRedirectParams } from '../utils/redirection';
|
||||
import { createDeleteUserToken } from '../middlewares/user-token';
|
||||
|
||||
@@ -34,6 +37,7 @@ module.exports = function enableAuthentication(app) {
|
||||
// enable loopback access control authentication. see:
|
||||
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
|
||||
app.enableAuth();
|
||||
const ifNotMobile = ifNotMobileRedirect();
|
||||
const ifUserRedirect = ifUserRedirectTo();
|
||||
const ifNoUserRedirect = ifNoUserRedirectHome();
|
||||
const devSaveAuthCookies = devSaveResponseAuthCookies();
|
||||
@@ -87,6 +91,8 @@ module.exports = function enableAuthentication(app) {
|
||||
createGetPasswordlessAuth(app)
|
||||
);
|
||||
|
||||
api.get('/mobile-login', ifNotMobile, ifUserRedirect, mobileLogin(app));
|
||||
|
||||
app.use(api);
|
||||
};
|
||||
|
||||
@@ -188,3 +194,53 @@ function createGetPasswordlessAuth(app) {
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function mobileLogin(app) {
|
||||
const {
|
||||
models: { User }
|
||||
} = app;
|
||||
return async function getPasswordlessAuth(req, res, next) {
|
||||
try {
|
||||
const auth0Res = await fetch(
|
||||
`https://${process.env.AUTH0_DOMAIN}/userinfo`,
|
||||
{
|
||||
headers: { Authorization: req.headers.authorization }
|
||||
}
|
||||
);
|
||||
|
||||
if (!auth0Res.ok) {
|
||||
return next(
|
||||
wrapHandledError(new Error('Invalid Auth0 token'), {
|
||||
type: 'danger',
|
||||
message: 'We could not log you in, please try again in a moment.',
|
||||
status: auth0Res.status
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const { email } = await auth0Res.json();
|
||||
|
||||
if (!isEmail(email)) {
|
||||
return next(
|
||||
wrapHandledError(new TypeError('decoded email is invalid'), {
|
||||
type: 'danger',
|
||||
message: 'The email is incorrectly formatted',
|
||||
status: 400
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
User.findOne$({ where: { email } })
|
||||
.do(async user => {
|
||||
if (!user) {
|
||||
user = await User.create({ email });
|
||||
}
|
||||
await user.mobileLoginByRequest(req, res);
|
||||
res.end();
|
||||
})
|
||||
.subscribe(() => {}, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"./middlewares/constant-headers": {},
|
||||
"./middlewares/csp": {},
|
||||
"./middlewares/flash-cheaters": {},
|
||||
"./middlewares/passport-login": {}
|
||||
"./middlewares/passport-login": {},
|
||||
"./middlewares/rate-limit": {
|
||||
"paths": ["/mobile-login"]
|
||||
}
|
||||
},
|
||||
"files": {},
|
||||
"final:after": {
|
||||
|
||||
19
api-server/src/server/middlewares/rate-limit.js
Normal file
19
api-server/src/server/middlewares/rate-limit.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import MongoStore from 'rate-limit-mongo';
|
||||
|
||||
const url = process.env.MONGODB || process.env.MONGOHQ_URL;
|
||||
|
||||
// Rate limit for mobile login
|
||||
// 10 requests per 15 minute windows
|
||||
export default function rateLimitMiddleware() {
|
||||
return rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new MongoStore({
|
||||
uri: url,
|
||||
expireTimeMs: 15 * 60 * 1000
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -26,6 +26,7 @@ const updateHooksRE = /^\/hooks\/update-paypal$/;
|
||||
// note: this would be replaced by webhooks later
|
||||
const donateRE = /^\/donate\/charge-stripe$/;
|
||||
const submitCoderoadChallengeRE = /^\/coderoad-challenge-completed$/;
|
||||
const mobileLoginRE = /^\/mobile-login\/?$/;
|
||||
|
||||
const _pathsAllowedREs = [
|
||||
authRE,
|
||||
@@ -41,7 +42,8 @@ const _pathsAllowedREs = [
|
||||
unsubscribeRE,
|
||||
updateHooksRE,
|
||||
donateRE,
|
||||
submitCoderoadChallengeRE
|
||||
submitCoderoadChallengeRE,
|
||||
mobileLoginRE
|
||||
];
|
||||
|
||||
export function isAllowedPath(path, pathsAllowedREs = _pathsAllowedREs) {
|
||||
|
||||
@@ -77,6 +77,20 @@ export function ifUserRedirectTo(status) {
|
||||
};
|
||||
}
|
||||
|
||||
export function ifNotMobileRedirect() {
|
||||
return (req, res, next) => {
|
||||
//
|
||||
// Todo: Use the below check once we have done more research on usage
|
||||
//
|
||||
// const isMobile = /(iPhone|iPad|Android)/.test(req.headers['user-agent']);
|
||||
// if (!isMobile) {
|
||||
// res.json({ error: 'not from mobile' });
|
||||
// } else {
|
||||
// next();
|
||||
// }
|
||||
next();
|
||||
};
|
||||
}
|
||||
// for use with express-validator error formatter
|
||||
export const createValidatorErrorHandler =
|
||||
(...args) =>
|
||||
|
||||
Reference in New Issue
Block a user