Skip to content

Commit

Permalink
Merge pull request #23 from williamchong007/develop
Browse files Browse the repository at this point in the history
Add user profile and civic liker oauth api
  • Loading branch information
williamchong committed Mar 14, 2019
2 parents 910ec7b + dd9ef04 commit 450d8b0
Show file tree
Hide file tree
Showing 25 changed files with 265 additions and 80 deletions.
2 changes: 2 additions & 0 deletions config/config.js
Expand Up @@ -9,10 +9,12 @@ config.FIRESTORE_PAYOUT_ROOT = process.env.FIRESTORE_PAYOUT_ROOT;
config.FIRESTORE_MISSION_ROOT = process.env.FIRESTORE_MISSION_ROOT;
config.FIRESTORE_CONFIG_ROOT = process.env.FIRESTORE_CONFIG_ROOT;
config.FIRESTORE_COUPON_ROOT = process.env.FIRESTORE_COUPON_ROOT;
config.FIRESTORE_OAUTH_CLIENT_ROOT = process.env.FIRESTORE_OAUTH_CLIENT_ROOT;
config.FIREBASE_STORAGE_BUCKET = process.env.FIREBASE_STORAGE_BUCKET;

config.JWT_PUBLIC_CERT_PATH = '';
config.JWT_PRIVATE_KEY_PATH = '';
config.PROVIDER_JWT_COMMON_SECRET = '';

config.INTERCOM_USER_HASH_SECRET = '';

Expand Down
3 changes: 3 additions & 0 deletions src/api.js
Expand Up @@ -9,6 +9,7 @@ import { supportedLocales } from './locales';
import errorHandler from './middleware/errorHandler';
import getPublicInfo from './routes/getPublicInfo';
import userChallenge from './routes/users/challenge';
import userGetInfo from './routes/users/apiGetInfo';
import missions from './routes/mission/missions';
import missionClaim from './routes/mission/claim';
import storeInvite from './routes/misc/storeInvite';
Expand Down Expand Up @@ -49,9 +50,11 @@ app.use((req, res, next) => {
// });
app.use('/api', getPublicInfo);
app.use('/api/users', userChallenge);
app.use('/api/users', userGetInfo);

app.use(getPublicInfo);
app.use('/users', userChallenge);
app.use('/users', userGetInfo);
app.use('/mission', missions);
app.use('/mission', missionClaim);
app.use('/misc', storeInvite);
Expand Down
118 changes: 118 additions & 0 deletions src/middleware/jwt.js
@@ -0,0 +1,118 @@
import { setNoCacheHeader } from './noCache';
import {
getProviderJWTSecret,
publicKey as verifySecret,
defaultAudience,
getToken,
issuer,
} from '../util/jwt';
import {
oAuthClientCollection as oAuthClientDbRef,
} from '../util/firebase';

const expressjwt = require('express-jwt');
const jwt = require('jsonwebtoken');

async function fetchProviderClientSecret(clientId) {
const spClient = await oAuthClientDbRef.doc(clientId).get();
if (!spClient.exists) throw new Error('INVALID_AZP');
const {
secret,
} = spClient.data();
return secret;
}

function checkPermissions(inputScopes, target) {
let scopes = inputScopes;
if (!scopes) return false;
if (!Array.isArray(scopes)) scopes = scopes.split(' ');
let targets = target.split(':');
if (targets.length > 1) {
const permission = targets[0];
const subScopes = targets[1].split('.');
let lastScope = `${permission}:${subScopes[0]}`;
const list = [permission, lastScope];
for (let i = 1; i < subScopes.length; i += 1) {
const currentScope = `${lastScope}.${subScopes[i]}`;
list.push(currentScope);
lastScope = currentScope;
}
targets = list;
}
if (scopes.find(scope => targets.includes(scope))) return true;
return false;
}

export const jwtAuth = (
permission = 'read',
secret = verifySecret,
{ audience = defaultAudience } = {},
) => async (req, res, next) => {
setNoCacheHeader(res);
try {
const token = getToken(req);
const decoded = jwt.decode(token);
if (decoded.azp) {
const clientSecret = await fetchProviderClientSecret(decoded.azp);
secret = getProviderJWTSecret(clientSecret); // eslint-disable-line no-param-reassign
}
} catch (err) {
// no op
}
expressjwt({
secret,
getToken,
audience,
issuer,
})(req, res, (e) => {
if (e instanceof expressjwt.UnauthorizedError) {
res.status(401).send('LOGIN_NEEDED');
return;
}
if (!req.user
|| (permission && !req.user.permissions && !req.user.scope)
|| ((permission && !checkPermissions(req.user.permissions, permission))
&& (permission && !checkPermissions(req.user.scope, permission)))) {
res.status(401).send('INVALID_GRANT');
return;
}
next(e);
});
};

export const jwtOptionalAuth = (
permission = 'read',
secret = verifySecret,
{ audience = defaultAudience } = {},
) => async (req, res, next) => {
setNoCacheHeader(res);
try {
const token = getToken(req);
const decoded = jwt.decode(token);
if (decoded.azp) {
const clientSecret = await fetchProviderClientSecret(decoded.azp);
secret = getProviderJWTSecret(clientSecret); // eslint-disable-line no-param-reassign
}
} catch (err) {
// no op
}
expressjwt({
credentialsRequired: false,
secret,
getToken,
audience,
issuer,
})(req, res, (e) => {
if (e instanceof expressjwt.UnauthorizedError) {
next();
return;
}
if (!req.user
|| (permission && !req.user.permissions && !req.user.scope)
|| ((permission && !checkPermissions(req.user.permissions, permission))
&& (permission && !checkPermissions(req.user.scope, permission)))) {
req.user = undefined;
}
next(e);
});
};
11 changes: 11 additions & 0 deletions src/middleware/noCache.js
@@ -0,0 +1,11 @@
export function setNoCacheHeader(res) {
res.setHeader('Surrogate-Control', 'no-store');
res.setHeader(
'Cache-Control',
'no-store, no-cache, must-revalidate, proxy-revalidate',
);
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
}

export default setNoCacheHeader;
8 changes: 4 additions & 4 deletions src/routes/civic/index.js
Expand Up @@ -2,7 +2,7 @@ import { Router } from 'express';
import {
PUBSUB_TOPIC_MISC,
} from '../../constant';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import {
getUserWithCivicLikerProperties,
} from '../../util/api/users';
Expand Down Expand Up @@ -34,7 +34,7 @@ router.get('/quota', async (req, res, next) => {
}
});

router.put('/queue', jwtAuth('write'), async (req, res, next) => {
router.put('/queue', jwtAuth('write:civic_liker'), async (req, res, next) => {
try {
const userId = req.user.user;
const {
Expand Down Expand Up @@ -84,7 +84,7 @@ router.put('/queue', jwtAuth('write'), async (req, res, next) => {
});


router.delete('/queue', jwtAuth('write'), async (req, res, next) => {
router.delete('/queue', jwtAuth('write:civic_liker'), async (req, res, next) => {
try {
const userId = req.user.user;
const {
Expand Down Expand Up @@ -182,7 +182,7 @@ router.get('/trial/events/:id', async (req, res, next) => {
}
});

router.post('/trial/events/:eventId/join', jwtAuth('write'), async (req, res, next) => {
router.post('/trial/events/:eventId/join', jwtAuth('write:civic_liker'), async (req, res, next) => {
try {
const { eventId } = req.params;
const userId = req.user.user;
Expand Down
2 changes: 1 addition & 1 deletion src/routes/coupon/index.js
Expand Up @@ -14,7 +14,7 @@ import {
userCollection as userRef,
db,
} from '../../util/firebase';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { logClaimCouponTx } from '../../util/txLogger';
import publisher from '../../util/gcloudPub';

Expand Down
2 changes: 1 addition & 1 deletion src/routes/misc/storeInvite.js
@@ -1,5 +1,5 @@
import { Router } from 'express';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import publisher from '../../util/gcloudPub';
import {
userCollection as dbRef,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/mission/claim.js
@@ -1,6 +1,6 @@
import { Router } from 'express';
import { BigNumber } from 'bignumber.js';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import {
ETH_NETWORK_NAME,
PUBSUB_TOPIC_MISC,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/mission/getInfo.js
Expand Up @@ -9,7 +9,7 @@ import {
} from '../../util/ValidationHelper';
import { ValidationError } from '../../util/ValidationError';
import publisher from '../../util/gcloudPub';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import {
userCollection as dbRef,
missionCollection as missionsRef,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/payment/index.js
Expand Up @@ -12,7 +12,7 @@ import {
} from '../../util/ValidationHelper';
import { logTransferDelegatedTx, logETHTx } from '../../util/txLogger';
import { web3, LikeCoin, sendTransactionWithLoop } from '../../util/web3';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import publisher from '../../util/gcloudPub';
import {
userCollection as dbRef,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/referral/index.js
Expand Up @@ -8,7 +8,7 @@ import {
filterMissionData,
filterPayoutData,
} from '../../util/ValidationHelper';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';

function getIfReferralMissionDone(m, { u }) {
const { id } = m;
Expand Down
2 changes: 1 addition & 1 deletion src/routes/social/facebook.js
@@ -1,7 +1,7 @@
import { Router } from 'express';
import { PUBSUB_TOPIC_MISC } from '../../constant';
import publisher from '../../util/gcloudPub';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { checkPlatformAlreadyLinked, socialLinkFacebook } from '../../util/api/social';
import { ValidationError } from '../../util/ValidationError';
import { userCollection as dbRef } from '../../util/firebase';
Expand Down
2 changes: 1 addition & 1 deletion src/routes/social/flickr.js
Expand Up @@ -3,7 +3,7 @@ import { checkPlatformAlreadyLinked } from '../../util/api/social';
import { fetchFlickrOAuthInfo, fetchFlickrUser } from '../../util/oauth/flickr';
import { PUBSUB_TOPIC_MISC } from '../../constant';
import publisher from '../../util/gcloudPub';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { ValidationError } from '../../util/ValidationError';
import {
userCollection as dbRef,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/social/link.js
@@ -1,5 +1,5 @@
import { Router } from 'express';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { ValidationError } from '../../util/ValidationError';
import { LINK_ICON_TYPES } from '../../constant';
import { isValidSocialLink } from '../../util/api/social';
Expand Down
2 changes: 1 addition & 1 deletion src/routes/social/medium.js
Expand Up @@ -3,7 +3,7 @@ import { checkPlatformAlreadyLinked } from '../../util/api/social';
import { fetchMediumOAuthInfo, fetchMediumUser } from '../../util/oauth/medium';
import { PUBSUB_TOPIC_MISC } from '../../constant';
import publisher from '../../util/gcloudPub';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { ValidationError } from '../../util/ValidationError';
import {
userCollection as dbRef,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/social/setInfo.js
Expand Up @@ -10,7 +10,7 @@ import {
} from '../../util/firebase';
import { getLinkOrderMap } from '../../util/api/social';
import { tryToUnlinkOAuthLogin } from '../../util/api/users';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { ValidationError } from '../../util/ValidationError';
import {
filterSocialPlatformPersonal,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/social/twitter.js
Expand Up @@ -2,7 +2,7 @@ import { Router } from 'express';
import { fetchTwitterOAuthInfo } from '../../util/oauth/twitter';
import { PUBSUB_TOPIC_MISC } from '../../constant';
import publisher from '../../util/gcloudPub';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { checkPlatformAlreadyLinked, socialLinkTwitter } from '../../util/api/social';
import { ValidationError } from '../../util/ValidationError';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/tx/index.js
Expand Up @@ -6,7 +6,7 @@ import {
userCollection as dbRef,
txCollection as txLogRef,
} from '../../util/firebase';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import { ValidationError } from '../../util/ValidationError';
import {
filterTxData,
Expand Down
26 changes: 26 additions & 0 deletions src/routes/users/apiGetInfo.js
@@ -0,0 +1,26 @@
import { Router } from 'express';
import { jwtAuth } from '../../middleware/jwt';
import {
filterUserDataScoped,
} from '../../util/ValidationHelper';
import {
getUserWithCivicLikerProperties,
} from '../../util/api/users/getPublicInfo';

const router = Router();

router.get('/profile', jwtAuth('profile'), async (req, res, next) => {
try {
const username = req.user.user;
const payload = await getUserWithCivicLikerProperties(username);
if (payload) {
res.json(filterUserDataScoped(payload, req.user.scope));
} else {
res.sendStatus(404);
}
} catch (err) {
next(err);
}
});

export default router;
2 changes: 1 addition & 1 deletion src/routes/users/getInfo.js
Expand Up @@ -3,7 +3,7 @@ import { Router } from 'express';
import {
userCollection as dbRef,
} from '../../util/firebase';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import {
filterUserData,
} from '../../util/ValidationHelper';
Expand Down
2 changes: 1 addition & 1 deletion src/routes/users/registerLogin.js
Expand Up @@ -31,7 +31,7 @@ import {
checkUserNameValid,
} from '../../util/ValidationHelper';
import { handleAvatarUploadAndGetURL } from '../../util/fileupload';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import publisher from '../../util/gcloudPub';
import { getFirebaseUserProviderUserInfo } from '../../util/FirebaseApp';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/users/setInfo.js
@@ -1,5 +1,5 @@
import { Router } from 'express';
import { jwtAuth } from '../../util/jwt';
import { jwtAuth } from '../../middleware/jwt';
import {
userCollection as dbRef,
} from '../../util/firebase';
Expand Down

0 comments on commit 450d8b0

Please sign in to comment.