Skip to content

Commit

Permalink
feat(store-api): Improve project structure MVC. Add middleware for JW…
Browse files Browse the repository at this point in the history
…T validation and OpenFGA permission check.
  • Loading branch information
embesozzi committed Oct 14, 2023
1 parent 5e2bc01 commit f57bd09
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 171 deletions.
4 changes: 2 additions & 2 deletions store-openfga-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"name": "store-openfga-api",
"description": "An OAuth 2.0 API protected by OpenFGA for fine-grained authorization.",
"author": "Martin Besozzi <[email protected]>)",
"version": "1.0.0",
"version": "1.1.0",
"license": "MIT",
"main": "server.js",
"scripts": {
"start": "nodemon server.js",
"start": "nodemon src/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
Expand Down
169 changes: 0 additions & 169 deletions store-openfga-api/server.js

This file was deleted.

6 changes: 6 additions & 0 deletions store-openfga-api/src/config/jwt.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
jwksUri: process.env.OIDC_PROVIDER_JWKS_URI || "http://localhost:8081/realms/master/protocol/openid-connect/certs",
audience: process.env.OIDC_PROVIDER_AUDIENCE || "account",
issuer: process.env.OIDC_PROVIDER_DOMAIN || "http://localhost:8081/realms/master"
}

6 changes: 6 additions & 0 deletions store-openfga-api/src/config/openfga.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
apiScheme: process.env.OPENFGA_API_SCHEME || "http",
apiHost: process.env.OPENFGA_API_HOST || "openfga:8080"
}


24 changes: 24 additions & 0 deletions store-openfga-api/src/controllers/products.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const fga = require('../middlewares/openfga');

const productsService = require('../services/products.service');

const get = async function(req, res){
console.log(`[Store API] Getting product id: ${req.params.id}`);
res.send(productsService.get(req.params.id));
}

const getAll = async function(req, res){
console.log('[Store API] Getting products');
res.send(productsService.getAll());
}

const publish = async function(req, res){
console.log(`[Store API] Publishing product id: ${req.params.id}`);
res.send(productsService.publish(req.params.id));
}

module.exports = {
get,
getAll,
publish
};
50 changes: 50 additions & 0 deletions store-openfga-api/src/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module.exports = {
Products: [
{
id: 1,
name: 'Glasses Ray Ban',
category: "pre-sale",
description: 'Ray-Ban Black Original Wayfarer Classic Sunglasses',
url : "https://source.unsplash.com/K62u25Jk6vo/600x300",
details : "Brand <b>Ray-Ban</b> <br> Model Name Stories <br> Style Stories<br> Color Shiny Blue/Dark Blue Polarized <br> Age Range (Description) Adult",
status : "publish"
},
{
id: 2,
name: 'Apple watch',
category: "pre-sale",
description: 'Apple Watch Series 3 42MM Special Features',
url : "https://source.unsplash.com/2cFZ_FB08UM/600x300",
details : "Brand <b>Apple</b> <br> Model Name Apple Watch Series <br> Style GPS <br> Special Feature Activity Tracker, Heart Rate Monitor, Sleep Monitor, Blood Oxygen",
status : "publish"
},
{
id: 3,
name: 'Headphones Bose',
category: "pre-sale",
description: 'Bose Noise Cancelling Wireless Headphones 700',
url : "https://source.unsplash.com/vISNAATFXlE/600x300",
details: "Brand <b>Bose</b> <br> Audio Model Name <br> Performance ANC HeadphonesColor<br>Technology Bluetooth 5.0",
status: "published"
},
{
id: 4,
name: 'Nikon Camera',
category: "pre-sale",
description: "Nikon Camera Z50 Two Lens Coolpix B500",
url : "https://source.unsplash.com/dcgB3CgidlU/600x300",
status : "publish",
details: "Brand <b>Nikon</b> <br>Model Name Nikon Coolpix B500 <br>Form Factor Point and Shoot <br>Effective Still Resolution 16 MP",
status : "published"
},
{
id: 5,
name: 'Chanel N°5 Perfume',
category: "pre-sale",
description: 'Ulric De Varens Gold Issime Pour Elle 75ml Estilo Chanel Nº5.',
url : "https://source.unsplash.com/potCPE_Cw8A/600x300",
details : "Brand <b>CHANEL<b> <br>Item Form Spray <br>Item Volume 3.4 Fluid Ounces <br> Age Range (Description)Adult",
status : "published"
}
]
}
29 changes: 29 additions & 0 deletions store-openfga-api/src/middlewares/jwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const jwt = require("express-jwt");
const { decode } = require('jsonwebtoken');
const config = require("../config/jwt.config.js");
const jwksRsa = require("jwks-rsa");

validateToken = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: config.jwksUri
}),
audience: config.audience,
issuer: config.issuer,
algorithms: ["RS256"]
});

decodeToken = (req, res, next) => {
const auth = req.get('Authorization');
const { sub } = decode(auth.split(' ')[1]);
req.userId = sub;
return next();
};


module.exports = {
validateToken,
decodeToken
}
68 changes: 68 additions & 0 deletions store-openfga-api/src/middlewares/openfga.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const { OpenFgaApi } = require("@openfga/sdk");

const config = require("../config/openfga.config.js");
const { all } = require("../routes/products.route.js");

const fgaClient = new OpenFgaApi({
apiScheme: config.apiScheme,
apiHost: config.apiHost
});

const discoverStore = async () => {
try {
const { stores } = await fgaClient.listStores();
for (const store of stores) {
console.log(`[Store API] Store found name: ${store.name} id: ${store.id}`);
fgaClient.storeId = store.id;
}
return fgaClient;
}
catch(e){
throw new Error('OpenFGA is not initialized properly')
}
}

const getClient = async () => {
return (fgaClient.storeId) ? fgaClient : await discoverStore();
}

const checkTuple = async function (user, relation, object) {
console.log(`[Store API] Check tuple (user: '${user}', rel: '${relation}', obj: '${object}')`);
try {
let client = await getClient();
let { allowed } = await client.check({
tuple_key: {
user: user,
relation: relation,
object: object
}
});
console.log(`[Store API] Check tuple for user: ${user} isAllowed: ${allowed}`);
return allowed;
} catch ( e ) {
console.log(e);
return false;
}
}

const userHasRole = async (userId, roleName) => {
return await checkTuple( `user:${userId}`, "assignee", `role:${roleName}`);
}

const checkUserHasRole = (roleName) => {
return async (req, res, next) => {
let allowed = await userHasRole(req.userId, roleName)

if(allowed) {
next();
}
else {
res.status(403).send();
return;
}
}
}

module.exports = {
checkUserHasRole
}
10 changes: 10 additions & 0 deletions store-openfga-api/src/routes/index.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const express = require('express');
const products = require('./products.route');

const router = express.Router();

router.use('/api/products', products);

router.get('/', (req, res) => res.send('API version 1.0.0'));

module.exports = router;
Loading

0 comments on commit f57bd09

Please sign in to comment.