add custom authentication checks
This commit is contained in:
committed by
Daniel Hutzel
parent
3cf02cb567
commit
70b0c85346
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,3 +1,5 @@
|
||||
using {managed} from '@sap/cds/common';
|
||||
|
||||
namespace sap.capire.media.store;
|
||||
|
||||
aspect Named {
|
||||
@@ -17,7 +19,7 @@ aspect Person {
|
||||
phone : String(24);
|
||||
fax : String(24);
|
||||
email : String(60);
|
||||
password : String(111);
|
||||
password : String(500);
|
||||
}
|
||||
|
||||
entity MediaTypes : Named {}
|
||||
@@ -88,7 +90,7 @@ entity InvoiceItems {
|
||||
quantity : Integer default 1;
|
||||
}
|
||||
|
||||
entity Tracks {
|
||||
entity Tracks : managed {
|
||||
key ID : Integer;
|
||||
name : String(200);
|
||||
album : Association to Albums;
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@sap/cds": "^4.2.8",
|
||||
"bcrypt": "^5.0.0",
|
||||
"express": "^4",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"moment": "^2.29.1",
|
||||
"passport": "^0.4.1"
|
||||
},
|
||||
@@ -17,10 +19,10 @@
|
||||
"scripts": {
|
||||
"start": "npx cds run",
|
||||
"deploy": "cds deploy --to sqlite:mychinook.db",
|
||||
"rebuild": "npm run deploy && npm run start",
|
||||
"test": "mocha test/media-service.test.js --verbose --timeout 10000"
|
||||
},
|
||||
"cds": {
|
||||
"ACCESS_TOKEN_SECRET": "secret",
|
||||
"requires": {
|
||||
"db": {
|
||||
"kind": "sqlite",
|
||||
@@ -30,30 +32,7 @@
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"strategy": "mock",
|
||||
"users": {
|
||||
"andrew@chinookcorp.com": {
|
||||
"password": "some",
|
||||
"roles": [
|
||||
"employee"
|
||||
],
|
||||
"userAttributes": {
|
||||
"level": 1,
|
||||
"ID": 1
|
||||
}
|
||||
},
|
||||
"luisg@embraer.com.br": {
|
||||
"password": "some",
|
||||
"roles": [
|
||||
"customer"
|
||||
],
|
||||
"userAttributes": {
|
||||
"level": 0,
|
||||
"ID": 1
|
||||
}
|
||||
},
|
||||
"*": true
|
||||
}
|
||||
"impl": "srv/auth.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,16 @@ cds.on("bootstrap", (app) => {
|
||||
});
|
||||
// add your own middleware before any by cds are added
|
||||
});
|
||||
cds.on("served", async ({ db }) => {
|
||||
cds.on("served", async ({ db, messaging, ...servedServices }) => {
|
||||
// import data from chinook db if needed
|
||||
await importData(db);
|
||||
// add more middleware after all CDS servies
|
||||
|
||||
// add logging current user before any request
|
||||
for (let i in servedServices) {
|
||||
servedServices[i].prepend((srv) =>
|
||||
srv.before("*", (req) => console.log("[USER]:", req.user))
|
||||
);
|
||||
}
|
||||
});
|
||||
// delegate to default server.js:
|
||||
|
||||
module.exports = cds.server;
|
||||
|
||||
29
media-store/srv/auth.js
Normal file
29
media-store/srv/auth.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const cds = require("@sap/cds");
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
const { ACCESS_TOKEN_SECRET } = cds.env;
|
||||
class MyUser extends cds.User {
|
||||
constructor(attr, roles, id) {
|
||||
super({ attr, _roles: roles, id });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
const { authorization: authHeader } = req.headers;
|
||||
const token = authHeader && authHeader.split(" ")[1];
|
||||
if (token === null) {
|
||||
return res.sendStatus(401);
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedUser = jwt.verify(token, ACCESS_TOKEN_SECRET);
|
||||
req.user = new MyUser(
|
||||
{ ID: decodedUser.ID },
|
||||
decodedUser.roles,
|
||||
decodedUser.email
|
||||
);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
next();
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
using {sap.capire.media.store as my} from '../db/schema';
|
||||
using {BrowseTracks.Tracks} from './browse-tracks-service';
|
||||
|
||||
@(requires : 'customer')
|
||||
service BrowseInvoices {
|
||||
|
||||
service BrowseInvoices @(requires : 'customer') {
|
||||
@readonly
|
||||
entity Invoices as projection on my.Invoices;
|
||||
|
||||
@@ -13,6 +13,10 @@ service BrowseInvoices {
|
||||
|
||||
action cancelInvoice(ID : Integer);
|
||||
|
||||
/*
|
||||
Below entities exposed
|
||||
due to 'navigation property errors' when expanding with odata
|
||||
*/
|
||||
@readonly
|
||||
entity Tracks as projection on my.Tracks excluding {
|
||||
alreadyOrdered
|
||||
|
||||
@@ -16,16 +16,11 @@ module.exports = async function () {
|
||||
const db = await cds.connect.to("db"); // connect to database service
|
||||
const { Invoices, InvoiceItems } = db.entities;
|
||||
|
||||
this.before("*", (req) => {
|
||||
console.log(
|
||||
"[USER]:",
|
||||
req.user.id,
|
||||
" [LEVEL]: ",
|
||||
req.user.attr.level,
|
||||
"[ROLE]",
|
||||
req.user.is("user") ? "user" : "other"
|
||||
);
|
||||
});
|
||||
// this.before("*", (req) => {
|
||||
// if (!req.user.is("customer")) {
|
||||
// req.reject(403);
|
||||
// }
|
||||
// });
|
||||
|
||||
this.on("READ", "Invoices", async (req) => {
|
||||
return await db.run(req.query.where({ customer_ID: req.user.attr.ID }));
|
||||
|
||||
@@ -2,24 +2,36 @@ using {sap.capire.media.store as my} from '../db/schema';
|
||||
|
||||
service BrowseTracks {
|
||||
@readonly
|
||||
entity Tracks as projection on my.Tracks excluding {
|
||||
entity Tracks as projection on my.Tracks excluding {
|
||||
alreadyOrdered
|
||||
};
|
||||
|
||||
@(requires : 'authenticated-user')
|
||||
@readonly
|
||||
entity MarkedTracks as projection on my.Tracks;
|
||||
entity MarkedTracks @(restrict : [
|
||||
{
|
||||
grant : ['*', ],
|
||||
to : 'customer'
|
||||
},
|
||||
{
|
||||
grant : '*',
|
||||
to : 'employee'
|
||||
},
|
||||
]) as projection on my.Tracks;
|
||||
|
||||
/*
|
||||
Below entities exposed
|
||||
due to 'navigation property errors' when expanding with odata
|
||||
*/
|
||||
@readonly
|
||||
entity Genres as projection on my.Genres {
|
||||
entity Genres as projection on my.Genres {
|
||||
* , tracks : redirected to Tracks
|
||||
};
|
||||
|
||||
@readonly
|
||||
entity Albums as projection on my.Albums {
|
||||
entity Albums as projection on my.Albums {
|
||||
* , tracks : redirected to Tracks
|
||||
};
|
||||
|
||||
@readonly
|
||||
entity Artists as projection on my.Artists;
|
||||
entity Artists as projection on my.Artists;
|
||||
}
|
||||
|
||||
@@ -6,26 +6,21 @@ const selectTracksByEmail = (email) => `
|
||||
join sap_capire_media_store_Invoices invoices
|
||||
on tracks.ID = invoiceItems.track_ID
|
||||
join sap_capire_media_store_InvoiceItems invoiceItems
|
||||
on (invoices.ID = invoiceItems.invoice_ID and invoices.status='2') or
|
||||
(invoices.ID = invoiceItems.invoice_ID and invoices.status='1')
|
||||
on invoices.ID = invoiceItems.invoice_ID
|
||||
join sap_capire_media_store_Customers customers
|
||||
on customers.ID = invoices.customer_ID
|
||||
where customers.email='${email}'
|
||||
where (customers.email='${email}' and invoices.status='2')
|
||||
or (customers.email='${email}' and invoices.status='1')
|
||||
`;
|
||||
|
||||
module.exports = async function () {
|
||||
const db = await cds.connect.to("db"); // connect to database service
|
||||
|
||||
this.before("*", (req) => {
|
||||
console.log(
|
||||
"[USER]:",
|
||||
req.user.id,
|
||||
" [LEVEL]: ",
|
||||
req.user.attr.level,
|
||||
"[ROLE]",
|
||||
req.user.is("user") ? "user" : "other"
|
||||
);
|
||||
});
|
||||
// this.before("READ", "MarkedTracks", (req) => {
|
||||
// if (!req.user.is("customer")) {
|
||||
// req.reject(403);
|
||||
// }
|
||||
// });
|
||||
|
||||
this.on("READ", "MarkedTracks", async (req) => {
|
||||
const myTrackIds = (await db.run(selectTracksByEmail(req.user.id))).map(
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using {sap.capire.media.store as my} from '../db/schema';
|
||||
|
||||
@(requires : 'employee')
|
||||
service ManageStore {
|
||||
entity Tracks as projection on my.Tracks;
|
||||
action addTrack(name : String(25), albumTitle : String(255), genreName : String(255), composer : String(255));
|
||||
entity Albums as projection on my.Albums;
|
||||
entity Genres as projection on my.Genres;
|
||||
service ManageStore @(requires : 'employee') {
|
||||
entity Tracks as projection on my.Tracks;
|
||||
entity Albums as projection on my.Albums;
|
||||
entity Artists as projection on my.Artists;
|
||||
/*
|
||||
Below entities exposed
|
||||
due to errors when creating Tracks/Albums/Artists
|
||||
*/
|
||||
entity MediaTypes as projection on my.MediaTypes;
|
||||
entity Genres as projection on my.Genres;
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ const cds = require("@sap/cds");
|
||||
module.exports = async function () {
|
||||
const db = await cds.connect.to("db"); // connect to database service
|
||||
|
||||
const { Genres, Albums } = db.entities;
|
||||
const { Albums, Tracks, Artists } = db.entities;
|
||||
|
||||
this.before("*", (req) => {
|
||||
console.log(
|
||||
"[USER]:",
|
||||
req.user.id,
|
||||
" [LEVEL]: ",
|
||||
req.user.attr.level,
|
||||
"[ROLE]",
|
||||
req.user.is("user") ? "user" : "other"
|
||||
this.before("CREATE", "Tracks", async (req) => {
|
||||
let { ID: lastTrackId } = await db.run(
|
||||
SELECT.one(Tracks).columns("ID").orderBy({ ID: "desc" })
|
||||
);
|
||||
req.data = { ...req.data, ID: ++lastTrackId };
|
||||
});
|
||||
|
||||
this.on("addTrack", async (req) => {
|
||||
const { albumTitle, genreName, name: trackName, composer } = req.data;
|
||||
|
||||
const genre = await db.run(SELECT.one(Genres).where({ name: genreName }));
|
||||
const album = await db.run(SELECT.one(Albums).where({ title: albumTitle }));
|
||||
// todo impl
|
||||
this.before("CREATE", "Artists", async (req) => {
|
||||
let { ID: lastArtistId } = await db.run(
|
||||
SELECT.one(Artists).columns("ID").orderBy({ ID: "desc" })
|
||||
);
|
||||
req.data = { ...req.data, ID: ++lastArtistId };
|
||||
});
|
||||
this.before("CREATE", "Albums", async (req) => {
|
||||
let { ID: lastAlbumId } = await db.run(
|
||||
SELECT.one(Albums).columns("ID").orderBy({ ID: "desc" })
|
||||
);
|
||||
req.data = { ...req.data, ID: ++lastAlbumId };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -22,17 +22,34 @@ service Users {
|
||||
email : String(60);
|
||||
}
|
||||
|
||||
@(requires : 'authenticated-user')
|
||||
@(restrict : [
|
||||
{
|
||||
grant : '*',
|
||||
to : 'customer'
|
||||
},
|
||||
{
|
||||
grant : '*',
|
||||
to : 'employee'
|
||||
},
|
||||
])
|
||||
action updatePerson(person : Person);
|
||||
|
||||
@(requires : 'authenticated-user')
|
||||
@(restrict : [
|
||||
{
|
||||
grant : '*',
|
||||
to : 'customer'
|
||||
},
|
||||
{
|
||||
grant : '*',
|
||||
to : 'employee'
|
||||
},
|
||||
])
|
||||
function getPerson() returns Person;
|
||||
|
||||
function mockLogin(email : String(111), password : String(200)) returns {
|
||||
roles : array of String(111);
|
||||
level : Integer;
|
||||
mockedToken : String(500);
|
||||
email : my.Person.email;
|
||||
ID : my.Person.ID
|
||||
action login(email : String(111), password : String(200)) returns {
|
||||
roles : array of String(111);
|
||||
token : String(500);
|
||||
email : String(500);
|
||||
ID : Integer;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const cds = require("@sap/cds");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const bcrypt = require("bcrypt");
|
||||
|
||||
const USER_LEVELS = { customer: 1, employee: 2 };
|
||||
const { ACCESS_TOKEN_SECRET } = cds.env;
|
||||
const ACCESS_TOKEN_EXP_IN = "10m";
|
||||
|
||||
module.exports = async function () {
|
||||
const db = await cds.connect.to("db");
|
||||
@@ -8,17 +11,6 @@ module.exports = async function () {
|
||||
|
||||
const getUserEntity = (isCustomer) => (isCustomer ? Customers : Employees);
|
||||
|
||||
this.before("*", (req) => {
|
||||
console.log(
|
||||
"[USER]:",
|
||||
req.user.id,
|
||||
" [LEVEL]: ",
|
||||
req.user.attr.level,
|
||||
"[ROLE]",
|
||||
req.user.is("user") ? "user" : "other"
|
||||
);
|
||||
});
|
||||
|
||||
this.on("updatePerson", async (req) => {
|
||||
await UPDATE(
|
||||
getUserEntity(req.user && req.user._roles && req.user.is("customer"))
|
||||
@@ -48,25 +40,37 @@ module.exports = async function () {
|
||||
);
|
||||
});
|
||||
|
||||
this.on("mockLogin", async (req) => {
|
||||
this.on("login", async (req) => {
|
||||
const { email, password } = req.data;
|
||||
|
||||
let userFromDb = await db.run(SELECT.one(Employees).where({ email }));
|
||||
let role = "employee";
|
||||
let roles = ["employee"];
|
||||
if (!userFromDb) {
|
||||
userFromDb = await db.run(SELECT.one(Customers).where({ email }));
|
||||
role = "customer";
|
||||
roles = ["customer"];
|
||||
}
|
||||
if (!userFromDb || password !== userFromDb.password) {
|
||||
|
||||
const userEqualPassword = await bcrypt.compare(
|
||||
password,
|
||||
userFromDb.password
|
||||
);
|
||||
if (!userEqualPassword) {
|
||||
req.reject(401);
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ email, ID: userFromDb.ID, roles },
|
||||
ACCESS_TOKEN_SECRET,
|
||||
{
|
||||
expiresIn: ACCESS_TOKEN_EXP_IN,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
mockedToken: Buffer.from(`${email}:${password}`).toString("base64"),
|
||||
level: USER_LEVELS[role],
|
||||
token,
|
||||
roles,
|
||||
email: userFromDb.email,
|
||||
ID: userFromDb.ID,
|
||||
roles: [role],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const { resolve } = require("@sap/cds");
|
||||
const cds = require("@sap/cds");
|
||||
const bcrypt = require("bcrypt");
|
||||
const saltRounds = 10;
|
||||
|
||||
const FIRST_INDEX = 0;
|
||||
const ZERO_VALUE = 0;
|
||||
@@ -74,6 +77,16 @@ async function importData(targetDb) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedPassword = await new Promise((resolve, reject) =>
|
||||
bcrypt.hash("some", saltRounds, (error, hash) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve(hash);
|
||||
})
|
||||
);
|
||||
console.log("hashedPassword", hashedPassword);
|
||||
|
||||
for (index in targetCSNEntities) {
|
||||
const targetEntityName = targetCSNEntitiesNames[index];
|
||||
console.log(`[LOG]: Processing ${targetEntityName}`);
|
||||
@@ -110,7 +123,7 @@ async function importData(targetDb) {
|
||||
columns.push("password");
|
||||
srcResultRows = srcResultRows.map((row) => ({
|
||||
...row,
|
||||
password: "some",
|
||||
password: hashedPassword,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user