Skip to content

Commit

Permalink
feat: add toke service
Browse files Browse the repository at this point in the history
feat: add `issuedToken` model

To store the token that admin issued.

feat: add token issuer web page

feat: add token manager web page

feat: add token issue api

feat: add get tokens api

feat: add delete token api

feat: add admin login passport

Login admin user to use token issuser and manager
  • Loading branch information
Chinlinlee committed Sep 10, 2021
1 parent 0071d8d commit f1541ef
Show file tree
Hide file tree
Showing 54 changed files with 31,221 additions and 1,526 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ api/FHIR/*
models/mongodb/model/*
!models/mongodb/model/Patient*.js
!models/mongodb/model/FHIRStoredID.js
!models/mongodb/model/issuedToken.js


#ignore temp folder
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ MONGODB_SLAVEMODE=false
FHIRSERVER_HOST="localhost"
FHIRSERVER_PORT=8088
FHIRSERVER_APIPATH="fhir"
#If u want to use token auth, add below.
ENABLE_TOKEN_AUTH=true
ADMIN_LOGIN_PATH="adminLogin"
ADMIN_USERNAME="adminUsername"
ADMIN_PASSWORD="adminPassword"
```
After configuration, run `npm run build` to generate resources
```
Expand Down
4 changes: 4 additions & 0 deletions api/FHIR/Patient/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
} = require('../../../models/FHIR/httpMessage');
const _ = require('lodash');
const config = require('../../../config/config');
const { user } = require('../../apiService');

function setFormatWhenQuery(req, res) {
let format = _.get(req, "query._format");
Expand Down Expand Up @@ -46,6 +47,9 @@ router.use((req, res, next) => {
return res.send(handleError.exception(e));
}
});

router.use(user.tokenAuthentication);

if (_.get(config, "Patient.interaction.search", true)) {
router.get('/', FHIRValidateParams({
"_offset": joi.number().integer(),
Expand Down
90 changes: 89 additions & 1 deletion api/apiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const fetch = require('node-fetch');
const _ = require('lodash');
const AbortController = require('abort-controller');
const FHIR = require('../models/FHIR/fhir').Fhir;
const { handleError } = require('../models/FHIR/httpMessage');
const jwt = require('jsonwebtoken');
function getDeepKeys(obj) {
let keys = [];
for (let key in obj) {
Expand Down Expand Up @@ -134,8 +136,94 @@ async function checkReference(resourceData) {
};
}

const user = {
/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
* @returns
*/
checkIsLoggedIn: (req, res, next) => {
if (!req.isAuthenticated()) {
return res.status(401).send();
}
return next();
} ,
tokenAuthentication: async (req, res, next) => {
try {
if (process.env.ENABLE_TOKEN_AUTH == "true") {
let token = _.get(req.headers, "authorization",false);
if (!token) {
return res.status(400).send(handleError.security("missing authorization in headers"));
}
let tokenInDb = await mongodb.issuedToken.findOne({
token: token
});
if (tokenInDb) {
jwt.verify(tokenInDb, "AhKais7aij9tai7i", function(err, decoded) {
if (err) {
if (err.name == "TokenExpiredError") {
return res.status(401).send(handleError.expired("token expired"));
}
}
req.tokenObj = tokenInDb;
return next();
})
} else {
return res.status(401).send(handleError.security("the token not found"));
}
} else {
return next();
}
} catch(e) {
console.error(e);
return res.status(500).send(handleError.exception(e));
}
},
getTokenPermission: async (token,resourceType, interaction) => {
try {
let tokenInDb = await mongodb.issuedToken.findOne({
token: token
});
if (tokenInDb) {
let accessList = _.get(tokenInDb._doc,"accessList");
let resourceTypeInteractionList = accessList.find(v=> v.resourceType === resourceType);
if (resourceTypeInteractionList) {
let permission = _.get(resourceTypeInteractionList, interaction, false);
return permission;
}
return false;
}
return false;
} catch(e) {
console.error(e);
return false;
}
}
}
/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
* @returns
*/
user["checkTokenPermission"] = (req, res, next) => {
if (process.env.ENABLE_TOKEN_AUTH == "true") {
let permission = await user.getTokenPermission(req.tokenObj.token, resourceType, "create");
if (!permission) {
return res.status(403).send(handleError.forbidden("This token can not access this API."))
} else {
next();
}
}
next();
}

module.exports = {
getDeepKeys: getDeepKeys,
findResourceById: findResourceById,
checkReference: checkReference
checkReference: checkReference ,
user : user
}
25 changes: 25 additions & 0 deletions api/user/controller/deleteToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const mongodb = require('../../../models/mongodb');

/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = async function(req, res) {
try {
await mongodb.issuedToken.deleteOne({
_id: req.params._id
});
return res.status(200).send({
status: true,
message: "Delete success"
})
} catch(e) {
console.error(e);
return res.status(500).send({
status: false,
message: e
});
}

}
36 changes: 36 additions & 0 deletions api/user/controller/getIssuedToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const _ = require('lodash');
const mongodb = require('../../../models/mongodb');
/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = async function(req, res) {
if (req.user != process.env.ADMIN_USERNAME) {
return res.status(403).send();
}
let queryParameter = _.cloneDeep(req.query);
let paginationSkip = queryParameter['_offset'] == undefined ? 0 : queryParameter['_offset'];
let paginationLimit = queryParameter['_count'] == undefined ? 100 : queryParameter['_count'];
_.set(req.query, "_offset", paginationSkip);
_.set(req.query, "_count", paginationLimit);
delete queryParameter['_count'];
delete queryParameter['_offset'];
let docs = await mongodb.issuedToken.find({} , {
accessList: 1,
tokenName: 1,
tokenNote: 1,
_id: 1
}).
limit(paginationLimit).
skip(paginationSkip).
sort({
_id: -1
}).
exec();
let count = await mongodb.issuedToken.countDocuments({});
return res.send({
tokenList : docs,
total: count
});
}
9 changes: 9 additions & 0 deletions api/user/controller/getLoginStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

/**
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = function (req, res) {
return res.status(200).send(req.user);
}
25 changes: 25 additions & 0 deletions api/user/controller/postTokenIssue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

const jwt = require('jsonwebtoken');
const mongodb = require('../../../models/mongodb');

/**
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
module.exports = async function (req , res) {
try {
if (req.user != process.env.ADMIN_USERNAME) {
return res.status(403).send();
}
let token = jwt.sign(req.body , "AhKais7aij9tai7i" , {expiresIn: '1y'});
let tokenObj = new mongodb.issuedToken({
...req.body ,
token: `Bearer ${token}`
})
await tokenObj.save();
return res.status(200).send(tokenObj);
} catch(err) {
console.error(err);
return res.status(500).json(err);
}
}
58 changes: 58 additions & 0 deletions api/user/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const express = require('express');
const Joi = require('joi');
const router = express.Router();
const { user } = require('../apiService');
const { validateParams } = require('../validator');
const resourceTypeList = require('../../models/FHIR/resourceType');

router.post('/adminLogin', function (req , res , next) {
let passport = require('passport');
passport.authenticate('admin-login', function (err, user, info) {
if (err) { return next(err); }
if (!user) {
return res.status(401).json(info);
}
req.logIn(user, function (err) {
// Should not cause any errors
if (err) { return next(err); }
return res.json(user);
});
})(req, res, next);
//next(new Error("missing username or password"));
});

router.get('/loginStatus' , user.checkIsLoggedIn, require('./controller/getLoginStatus'));

router.post('/token/issue', user.checkIsLoggedIn, validateParams({
accessList: Joi.array().single().items(Joi.object().keys({
resourceType: Joi.string().valid(...resourceTypeList),
read: Joi.boolean().default(false),
vread: Joi.boolean().default(false),
create: Joi.boolean().default(false),
update: Joi.boolean().default(false),
"search-type": Joi.boolean().default(false),
history: Joi.boolean().default(false),
delete: Joi.boolean().default(false)
}).min(1)).required(),
tokenName: Joi.string().required(),
tokenNote: Joi.string()
}, "body" , {
allowUnknown: false
}) , require('./controller/postTokenIssue'));

router.get(
'/token',
user.checkIsLoggedIn,
validateParams({
"_offset": Joi.number().integer(),
"_count": Joi.number().integer()
}, "query" , {allowUnknown: false}),
require('./controller/getIssuedToken'));

router.delete(
'/token/:_id' ,
user.checkIsLoggedIn,
require('./controller/deleteToken'))


module.exports = router;
70 changes: 70 additions & 0 deletions models/mongodb/model/issuedToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
*
* @param {import('mongoose')} mongodb
* @returns
*/
module.exports = function (mongodb) {
let accessItemSchema = mongodb.Schema({
resourceType: {
type: String,
default: void 0
},
create: {
type: mongodb.SchemaTypes.Boolean,
default: false
} ,
delete: {
type: mongodb.SchemaTypes.Boolean,
default: false
},
read: {
type: mongodb.SchemaTypes.Boolean,
default: false
},
vread: {
type: mongodb.SchemaTypes.Boolean,
default: false
},
search: {
type: mongodb.SchemaTypes.Boolean,
default: false
},
history: {
type: mongodb.SchemaTypes.Boolean,
default: false
}
}, {
_id: false,
id: false,
versionKey: false
});

let issuedTokenSchema = mongodb.Schema({
token: {
type: String,
default: void 0
},
tokenName: {
type: String,
default: void 0
},
tokenNote: {
type: String,
default: void 0
},
accessList : {
type: [accessItemSchema] ,
default: void 0
}
},{
strict: false,
versionKey : false,
});

issuedTokenSchema.index({
"resourceType" : 1
});

let issuedToken = mongodb.model('issuedToken', issuedTokenSchema, 'issuedToken');
return issuedToken;
}
Loading

0 comments on commit f1541ef

Please sign in to comment.