From f86f5c1ba877ee2fc33f06208e988544b024efb9 Mon Sep 17 00:00:00 2001 From: William Chong Date: Mon, 11 Mar 2019 17:18:56 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8F=97=20Backport=20oauth=20jwt=20sup?= =?UTF-8?q?port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/middleware/jwt.js | 118 ++++++++++++++++++++++++++++++ src/middleware/noCache.js | 11 +++ src/routes/civic/index.js | 2 +- src/routes/coupon/index.js | 2 +- src/routes/misc/storeInvite.js | 2 +- src/routes/mission/claim.js | 2 +- src/routes/mission/getInfo.js | 2 +- src/routes/payment/index.js | 2 +- src/routes/referral/index.js | 2 +- src/routes/social/facebook.js | 2 +- src/routes/social/flickr.js | 2 +- src/routes/social/link.js | 2 +- src/routes/social/medium.js | 2 +- src/routes/social/setInfo.js | 2 +- src/routes/social/twitter.js | 2 +- src/routes/tx/index.js | 2 +- src/routes/users/getInfo.js | 2 +- src/routes/users/registerLogin.js | 2 +- src/routes/users/setInfo.js | 2 +- src/util/firebase.js | 2 + src/util/jwt.js | 109 +++++++++++++-------------- 21 files changed, 197 insertions(+), 77 deletions(-) create mode 100644 src/middleware/jwt.js create mode 100644 src/middleware/noCache.js diff --git a/src/middleware/jwt.js b/src/middleware/jwt.js new file mode 100644 index 000000000..32a24d951 --- /dev/null +++ b/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); + }); +}; diff --git a/src/middleware/noCache.js b/src/middleware/noCache.js new file mode 100644 index 000000000..4420842fb --- /dev/null +++ b/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; diff --git a/src/routes/civic/index.js b/src/routes/civic/index.js index e6ab5edc8..055e828af 100644 --- a/src/routes/civic/index.js +++ b/src/routes/civic/index.js @@ -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'; diff --git a/src/routes/coupon/index.js b/src/routes/coupon/index.js index bc18ef9d4..32bf6d036 100644 --- a/src/routes/coupon/index.js +++ b/src/routes/coupon/index.js @@ -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'; diff --git a/src/routes/misc/storeInvite.js b/src/routes/misc/storeInvite.js index 3f2f0bb96..e9ad8ef9b 100644 --- a/src/routes/misc/storeInvite.js +++ b/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, diff --git a/src/routes/mission/claim.js b/src/routes/mission/claim.js index 30172fad0..2d7ae7356 100644 --- a/src/routes/mission/claim.js +++ b/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, diff --git a/src/routes/mission/getInfo.js b/src/routes/mission/getInfo.js index 24dbffa25..24099adc9 100644 --- a/src/routes/mission/getInfo.js +++ b/src/routes/mission/getInfo.js @@ -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, diff --git a/src/routes/payment/index.js b/src/routes/payment/index.js index e7edc85f4..6c39c5ad8 100644 --- a/src/routes/payment/index.js +++ b/src/routes/payment/index.js @@ -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, diff --git a/src/routes/referral/index.js b/src/routes/referral/index.js index 6ebc44d42..9214d8334 100644 --- a/src/routes/referral/index.js +++ b/src/routes/referral/index.js @@ -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; diff --git a/src/routes/social/facebook.js b/src/routes/social/facebook.js index 0c5d1c240..daaac5190 100644 --- a/src/routes/social/facebook.js +++ b/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'; diff --git a/src/routes/social/flickr.js b/src/routes/social/flickr.js index 50bf733f9..7c9fd97dc 100644 --- a/src/routes/social/flickr.js +++ b/src/routes/social/flickr.js @@ -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, diff --git a/src/routes/social/link.js b/src/routes/social/link.js index 77f4cc228..e21ec4eb6 100644 --- a/src/routes/social/link.js +++ b/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'; diff --git a/src/routes/social/medium.js b/src/routes/social/medium.js index cd776774d..b5e4ac3c8 100644 --- a/src/routes/social/medium.js +++ b/src/routes/social/medium.js @@ -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, diff --git a/src/routes/social/setInfo.js b/src/routes/social/setInfo.js index 12e96dc2c..a86624021 100644 --- a/src/routes/social/setInfo.js +++ b/src/routes/social/setInfo.js @@ -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, diff --git a/src/routes/social/twitter.js b/src/routes/social/twitter.js index 1d8a38613..f570f9873 100644 --- a/src/routes/social/twitter.js +++ b/src/routes/social/twitter.js @@ -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 { diff --git a/src/routes/tx/index.js b/src/routes/tx/index.js index 30df123dc..80c3c62cc 100644 --- a/src/routes/tx/index.js +++ b/src/routes/tx/index.js @@ -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, diff --git a/src/routes/users/getInfo.js b/src/routes/users/getInfo.js index 59f4b2d6a..840593d38 100644 --- a/src/routes/users/getInfo.js +++ b/src/routes/users/getInfo.js @@ -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'; diff --git a/src/routes/users/registerLogin.js b/src/routes/users/registerLogin.js index f6dbe6df0..ef371020f 100644 --- a/src/routes/users/registerLogin.js +++ b/src/routes/users/registerLogin.js @@ -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 { diff --git a/src/routes/users/setInfo.js b/src/routes/users/setInfo.js index e94519b8a..981c9cf7d 100644 --- a/src/routes/users/setInfo.js +++ b/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'; diff --git a/src/util/firebase.js b/src/util/firebase.js index 4013cc19d..049a5b13f 100644 --- a/src/util/firebase.js +++ b/src/util/firebase.js @@ -10,6 +10,7 @@ import { FIRESTORE_PAYOUT_ROOT, FIRESTORE_COUPON_ROOT, FIRESTORE_CONFIG_ROOT, + FIRESTORE_OAUTH_CLIENT_ROOT, } from '../../config/config'; import serviceAccount from '../../config/serviceAccountKey.json'; @@ -36,6 +37,7 @@ export const missionCollection = getCollectionIfDefined(FIRESTORE_MISSION_ROOT); export const payoutCollection = getCollectionIfDefined(FIRESTORE_PAYOUT_ROOT); export const couponCollection = getCollectionIfDefined(FIRESTORE_COUPON_ROOT); export const configCollection = getCollectionIfDefined(FIRESTORE_CONFIG_ROOT); +export const oAuthClientCollection = getCollectionIfDefined(FIRESTORE_OAUTH_CLIENT_ROOT); export const bucket = FIREBASE_STORAGE_BUCKET ? admin.storage().bucket() : null; export { admin }; diff --git a/src/util/jwt.js b/src/util/jwt.js index 72d9f85fd..4ff6599dd 100644 --- a/src/util/jwt.js +++ b/src/util/jwt.js @@ -1,14 +1,16 @@ import { TEST_MODE, EXTERNAL_HOSTNAME } from '../constant'; +import { + PROVIDER_JWT_COMMON_SECRET, +} from '../../config/config'; const crypto = require('crypto'); const fs = require('fs'); const jwt = require('jsonwebtoken'); -const expressjwt = require('express-jwt'); const uuidv4 = require('uuid/v4'); -const config = require('../../config/config.js'); +const config = require('../../config/config'); -const audience = EXTERNAL_HOSTNAME; -const issuer = EXTERNAL_HOSTNAME; +export const defaultAudience = EXTERNAL_HOSTNAME; +export const issuer = EXTERNAL_HOSTNAME; let algorithm = 'RS256'; let signSecret; @@ -41,6 +43,15 @@ if (!signSecret || !verifySecret) { verifySecret = verifySecret || secret; } +export const publicKey = verifySecret; + +export function getProviderJWTSecret(clientSecret) { + const hash = crypto.createHmac('sha256', PROVIDER_JWT_COMMON_SECRET) + .update(clientSecret) + .digest('hex'); + return hash; +} + export function getToken(req) { if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { return req.headers.authorization.split(' ')[1]; @@ -48,73 +59,51 @@ export function getToken(req) { if (req.cookies && req.cookies.likecoin_auth) { return req.cookies.likecoin_auth; } + if (req.query && req.query.access_token) { + return req.query.access_token; + } return ''; } -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 const jwtVerify = ( token, - { ignoreExpiration } = {}, + secret = verifySecret, + { ignoreExpiration, audience = defaultAudience } = {}, ) => { const opt = { audience, issuer }; - return jwt.verify(token, verifySecret, { ...opt, ignoreExpiration }); + return jwt.verify(token, secret, { ...opt, ignoreExpiration }); }; -export const jwtSign = (payload) => { - const opt = { audience, issuer, algorithm }; - if (!payload.exp) opt.expiresIn = '30d'; +const internalSign = ( + payload, + secret, + opt = {}, +) => { + const options = opt; const jwtid = uuidv4(); - opt.jwtid = jwtid; - return { token: jwt.sign(payload, signSecret, opt), jwtid }; + options.jwtid = jwtid; + options.issuer = issuer; + options.mutatePayload = true; + const result = { ...payload }; + const token = jwt.sign(result, secret, options); + return { + token, + jwtid, + exp: result.exp, + }; }; -export const jwtAuth = (permission = 'read') => (req, res, next) => { - setNoCacheHeader(res); - expressjwt({ - secret: verifySecret, - getToken, - audience, - issuer, - })(req, res, (e) => { - if (e && e.name === 'UnauthorizedError') { - res.status(401).send('LOGIN_NEEDED'); - return; - } - if (!req.user - || !req.user.permissions - || (permission && !req.user.permissions.includes(permission))) { - res.status(401).send('MORE_AUTH_NEEDED'); - return; - } - next(e); - }); -}; +export const jwtSign = ( + payload, + { audience = defaultAudience, expiresIn = '30d' } = {}, +) => internalSign(payload, signSecret, { algorithm, audience, expiresIn }); -export const jwtOptionalAuth = (permission = 'read') => (req, res, next) => { - setNoCacheHeader(res); - expressjwt({ - credentialsRequired: false, - secret: verifySecret, - getToken, - audience, - issuer, - })(req, res, (e) => { - if (req.user - && req.user.permissions - && (permission && !req.user.permissions.includes(permission))) { - req.user = undefined; - } - next(e); - }); +export const jwtSignForAZP = ( + payload, + secret, + { audience = defaultAudience, expiresIn = '1h', azp } = {}, +) => { + const opt = { algorithm: 'HS256', audience }; + if (expiresIn) opt.expiresIn = expiresIn; + return internalSign({ ...payload, azp }, secret, opt); }; - -export default jwtAuth; From dd9ef040d98c1ebc8a1c461423215573c65346ea Mon Sep 17 00:00:00 2001 From: William Chong Date: Mon, 11 Mar 2019 18:35:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20Add=20user=20profile=20api=20?= =?UTF-8?q?=E2=9C=A8=20Add=20Civic=20Liker=20oauth=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.js | 2 ++ src/api.js | 3 +++ src/routes/civic/index.js | 6 +++--- src/routes/users/apiGetInfo.js | 26 ++++++++++++++++++++++++++ src/util/ValidationHelper.js | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/routes/users/apiGetInfo.js diff --git a/config/config.js b/config/config.js index 4a692c78f..c881877c0 100644 --- a/config/config.js +++ b/config/config.js @@ -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 = ''; diff --git a/src/api.js b/src/api.js index a88242c1d..fbed12ed6 100644 --- a/src/api.js +++ b/src/api.js @@ -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'; @@ -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); diff --git a/src/routes/civic/index.js b/src/routes/civic/index.js index 055e828af..4d48bdedc 100644 --- a/src/routes/civic/index.js +++ b/src/routes/civic/index.js @@ -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 { @@ -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 { @@ -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; diff --git a/src/routes/users/apiGetInfo.js b/src/routes/users/apiGetInfo.js new file mode 100644 index 000000000..4f8f3d035 --- /dev/null +++ b/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; diff --git a/src/util/ValidationHelper.js b/src/util/ValidationHelper.js index a0cdeaad9..4283a1beb 100644 --- a/src/util/ValidationHelper.js +++ b/src/util/ValidationHelper.js @@ -84,6 +84,40 @@ export function filterUserDataMin({ }; } +export function filterUserDataScoped(u, scope = []) { + const user = filterUserData(u); + let output = filterUserDataMin(u); + if (scope.includes('email')) output.email = user.email; + if (scope.includes('read:civic_liker')) { + const { + isPreRegCivicLiker, + preRegCivicLikerStatus, + isSubscribedCivicLiker, + isCivicLikerTrial, + isCivicLikerRenewalPeriod, + isExpiredCivicLiker, + civicLikerRenewalPeriodLast, + isHonorCivicLiker, + civicLikerSince, + civicLikerStatus, + } = user; + output = { + isPreRegCivicLiker, + preRegCivicLikerStatus, + isSubscribedCivicLiker, + isCivicLikerTrial, + isCivicLikerRenewalPeriod, + isExpiredCivicLiker, + civicLikerRenewalPeriodLast, + isHonorCivicLiker, + civicLikerSince, + civicLikerStatus, + ...output, + }; + } + return output; +} + export function filterTxData({ from, fromId,