add auth functionality
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
FROM node:20
|
FROM node:25
|
||||||
ADD src/package*.json /app/
|
ADD src/package*.json /app/
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ services:
|
|||||||
# - Set up auth: https://console.cloud.google.com/auth/overview
|
# - Set up auth: https://console.cloud.google.com/auth/overview
|
||||||
# GOOGLE_CLIENT_ID:
|
# GOOGLE_CLIENT_ID:
|
||||||
# GOOGLE_CLIENT_SECRET:
|
# GOOGLE_CLIENT_SECRET:
|
||||||
|
|
||||||
|
# PDP MODULES
|
||||||
|
# ===========
|
||||||
|
PDP_TYPE: cerbos
|
||||||
|
|
||||||
|
# Cerbos
|
||||||
|
# -----------
|
||||||
|
PDP_URL: http://jlinc-pdp:3592
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- jlinc-db
|
- jlinc-db
|
||||||
@@ -83,5 +91,16 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- jlinc
|
- jlinc
|
||||||
|
|
||||||
|
jlinc-pdp:
|
||||||
|
image: cerbos/cerbos:0.47.0
|
||||||
|
container_name: jlinc-pdp
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:3592:3592
|
||||||
|
volumes:
|
||||||
|
- ./policies:/policies
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- jlinc
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
jlinc:
|
jlinc:
|
||||||
|
|||||||
41
policies/data.yaml
Normal file
41
policies/data.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/Policy.schema.json
|
||||||
|
# docs: https://docs.cerbos.dev/cerbos/latest/policies/resource_policies
|
||||||
|
|
||||||
|
apiVersion: api.cerbos.dev/v1
|
||||||
|
resourcePolicy:
|
||||||
|
resource: data
|
||||||
|
version: default
|
||||||
|
rules:
|
||||||
|
- actions:
|
||||||
|
- create
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- actions:
|
||||||
|
- read
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- user
|
||||||
|
- admin
|
||||||
|
- thirdParty
|
||||||
|
- actions:
|
||||||
|
- update
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- actions:
|
||||||
|
- delete
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
|
||||||
|
# This is an example of using conditions for attribute-based access control
|
||||||
|
# The action is only allowed if the principal ID matches the ownerId attribute
|
||||||
|
# - actions:
|
||||||
|
# - someAction
|
||||||
|
# effect: EFFECT_ALLOW
|
||||||
|
# roles:
|
||||||
|
# - user
|
||||||
|
# condition:
|
||||||
|
# match:
|
||||||
|
# expr: request.resource.attr.ownerId == request.principal.id
|
||||||
41
policies/data_test.yaml
Normal file
41
policies/data_test.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/TestSuite.schema.json
|
||||||
|
# docs: https://docs.cerbos.dev/cerbos/latest/policies/compile#testing
|
||||||
|
|
||||||
|
name: dataTestSuite
|
||||||
|
description: Tests for verifying the data resource policy
|
||||||
|
tests:
|
||||||
|
- name: data actions
|
||||||
|
input:
|
||||||
|
principals:
|
||||||
|
- user#1
|
||||||
|
- admin#2
|
||||||
|
- thirdParty#3
|
||||||
|
resources:
|
||||||
|
- data#1
|
||||||
|
actions:
|
||||||
|
- create
|
||||||
|
- read
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
expected:
|
||||||
|
- resource: data#1
|
||||||
|
principal: user#1
|
||||||
|
actions:
|
||||||
|
create: EFFECT_DENY
|
||||||
|
read: EFFECT_ALLOW
|
||||||
|
update: EFFECT_DENY
|
||||||
|
delete: EFFECT_DENY
|
||||||
|
- resource: data#1
|
||||||
|
principal: admin#2
|
||||||
|
actions:
|
||||||
|
create: EFFECT_ALLOW
|
||||||
|
read: EFFECT_ALLOW
|
||||||
|
update: EFFECT_ALLOW
|
||||||
|
delete: EFFECT_ALLOW
|
||||||
|
- resource: data#1
|
||||||
|
principal: thirdParty#3
|
||||||
|
actions:
|
||||||
|
create: EFFECT_DENY
|
||||||
|
read: EFFECT_ALLOW
|
||||||
|
update: EFFECT_DENY
|
||||||
|
delete: EFFECT_DENY
|
||||||
40
policies/privateData.yaml
Normal file
40
policies/privateData.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/Policy.schema.json
|
||||||
|
# docs: https://docs.cerbos.dev/cerbos/latest/policies/resource_policies
|
||||||
|
|
||||||
|
apiVersion: api.cerbos.dev/v1
|
||||||
|
resourcePolicy:
|
||||||
|
resource: privateData
|
||||||
|
version: default
|
||||||
|
rules:
|
||||||
|
- actions:
|
||||||
|
- create
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- actions:
|
||||||
|
- read
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- user
|
||||||
|
- actions:
|
||||||
|
- update
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- actions:
|
||||||
|
- delete
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
|
||||||
|
# This is an example of using conditions for attribute-based access control
|
||||||
|
# The action is only allowed if the principal ID matches the ownerId attribute
|
||||||
|
# - actions:
|
||||||
|
# - someAction
|
||||||
|
# effect: EFFECT_ALLOW
|
||||||
|
# roles:
|
||||||
|
# - admin
|
||||||
|
# condition:
|
||||||
|
# match:
|
||||||
|
# expr: request.resource.attr.ownerId == request.principal.id
|
||||||
40
policies/privateData_test.yaml
Normal file
40
policies/privateData_test.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/Policy.schema.json
|
||||||
|
# docs: https://docs.cerbos.dev/cerbos/latest/policies/resource_policies
|
||||||
|
|
||||||
|
apiVersion: api.cerbos.dev/v1
|
||||||
|
resourcePolicy:
|
||||||
|
resource: privateData
|
||||||
|
version: default
|
||||||
|
rules:
|
||||||
|
- actions:
|
||||||
|
- create
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- actions:
|
||||||
|
- read
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- user
|
||||||
|
- actions:
|
||||||
|
- update
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
- actions:
|
||||||
|
- delete
|
||||||
|
effect: EFFECT_ALLOW
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
|
||||||
|
# This is an example of using conditions for attribute-based access control
|
||||||
|
# The action is only allowed if the principal ID matches the ownerId attribute
|
||||||
|
# - actions:
|
||||||
|
# - someAction
|
||||||
|
# effect: EFFECT_ALLOW
|
||||||
|
# roles:
|
||||||
|
# - admin
|
||||||
|
# condition:
|
||||||
|
# match:
|
||||||
|
# expr: request.resource.attr.ownerId == request.principal.id
|
||||||
19
policies/testdata/principals.yaml
vendored
Normal file
19
policies/testdata/principals.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/TestFixture/Principals.schema.json
|
||||||
|
# docs: https://docs.cerbos.dev/cerbos/latest/policies/compile#_sharing_test_fixtures
|
||||||
|
|
||||||
|
principals:
|
||||||
|
user#1:
|
||||||
|
id: user#1
|
||||||
|
roles:
|
||||||
|
- user
|
||||||
|
attr: {}
|
||||||
|
admin#2:
|
||||||
|
id: admin#2
|
||||||
|
roles:
|
||||||
|
- admin
|
||||||
|
attr: {}
|
||||||
|
thirdParty#3:
|
||||||
|
id: thirdParty#3
|
||||||
|
roles:
|
||||||
|
- thirdParty
|
||||||
|
attr: {}
|
||||||
12
policies/testdata/resources.yaml
vendored
Normal file
12
policies/testdata/resources.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/TestFixture/Resources.schema.json
|
||||||
|
# docs: https://docs.cerbos.dev/cerbos/latest/policies/compile#_sharing_test_fixtures
|
||||||
|
|
||||||
|
resources:
|
||||||
|
data#1:
|
||||||
|
id: data#1
|
||||||
|
kind: data
|
||||||
|
attr: {}
|
||||||
|
privateData#2:
|
||||||
|
id: privateData#2
|
||||||
|
kind: privateData
|
||||||
|
attr: {}
|
||||||
@@ -32,6 +32,8 @@ export async function loadConfig() {
|
|||||||
const { getModuleConfig } = await import(appModulePath);
|
const { getModuleConfig } = await import(appModulePath);
|
||||||
config.appModules[type] = getModuleConfig();
|
config.appModules[type] = getModuleConfig();
|
||||||
}
|
}
|
||||||
|
if (process.env.PDP_TYPE) config.pdpType = process.env.PDP_TYPE;
|
||||||
|
if (process.env.PDP_URL) config.pdpUrl = process.env.PDP_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"purposes": [],
|
||||||
|
"prohibitions": []
|
||||||
|
}
|
||||||
@@ -131,9 +131,12 @@ export async function populateAgreements() {
|
|||||||
withFileTypes: true,
|
withFileTypes: true,
|
||||||
});
|
});
|
||||||
for await (const file of files) {
|
for await (const file of files) {
|
||||||
const agreementId = file.name.slice(0, 36);
|
if (file.name.endsWith('.json'))
|
||||||
|
continue;
|
||||||
|
const agreementUuid = file.name.slice(0, 36);
|
||||||
const title = file.name.slice(39, file.name.length - 3);
|
const title = file.name.slice(39, file.name.length - 3);
|
||||||
const markdown = fs.readFileSync(path.join("./db/agreements", file.name), "utf8").trim();
|
const markdown = fs.readFileSync(path.join("./db/agreements", file.name), "utf8").trim();
|
||||||
|
const json = JSON.parse(fs.readFileSync(path.join("./db/agreements", file.name.replace('.md', '.json')), "utf8").trim());
|
||||||
const hash = createHash('sha256')
|
const hash = createHash('sha256')
|
||||||
.update(markdown)
|
.update(markdown)
|
||||||
.digest('hex')
|
.digest('hex')
|
||||||
@@ -141,40 +144,48 @@ export async function populateAgreements() {
|
|||||||
const agreementExists = await client.query(`
|
const agreementExists = await client.query(`
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
CASE
|
||||||
WHEN (SELECT COUNT(1) FROM agreement WHERE agreement_id_uuid = $1 AND user_id IS NULL) > 0
|
WHEN (SELECT COUNT(1) FROM agreement_content WHERE title = $1 AND hash = $2 AND user_id IS NULL) > 0
|
||||||
THEN TRUE
|
THEN TRUE
|
||||||
ELSE FALSE
|
ELSE FALSE
|
||||||
END AS exists
|
END AS exists
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
agreementId,
|
title,
|
||||||
|
hash,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
if (!agreementExists.rows[0].exists) {
|
if (!agreementExists.rows[0].exists) {
|
||||||
console.log(`Adding agreement '${title}' (${agreementId})`);
|
console.log(`Adding agreement '${title}' (${agreementUuid})`);
|
||||||
await client.query(`
|
await client.query(`
|
||||||
INSERT INTO agreement_content (
|
INSERT INTO agreement_content (
|
||||||
title,
|
title,
|
||||||
markdown,
|
markdown,
|
||||||
hash
|
hash,
|
||||||
|
key,
|
||||||
|
agreement_uuid
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
$3
|
$3,
|
||||||
) ON CONFLICT DO NOTHING;
|
$4,
|
||||||
|
$5
|
||||||
|
);
|
||||||
`, [
|
`, [
|
||||||
title,
|
title,
|
||||||
markdown,
|
markdown,
|
||||||
hash,
|
hash,
|
||||||
|
json.key,
|
||||||
|
agreementUuid,
|
||||||
]);
|
]);
|
||||||
const agreement = {
|
const agreement = {
|
||||||
|
"@context": json["@context"] || 'https://protocol.jlinc.org/context/jlinc-v7.jsonld',
|
||||||
uri: `${config.publicCoreUrl}/agreements/${hash}`,
|
uri: `${config.publicCoreUrl}/agreements/${hash}`,
|
||||||
purposes: [],
|
purposes: json.purposes || [],
|
||||||
caveats: [],
|
prohibitions: json.prohibitions || [],
|
||||||
shortNames: [],
|
shortNames: [],
|
||||||
validRoles: [],
|
validRoles: json.validRoles || [],
|
||||||
}
|
}
|
||||||
await data.agreement.create(agreement, null, client, agreementId);
|
await data.agreement.create(agreement, null, client, agreementUuid);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
46
src/db/migrations/000010 - prohibition.sql
Normal file
46
src/db/migrations/000010 - prohibition.sql
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
ALTER TABLE public.caveat RENAME TO prohibition;
|
||||||
|
ALTER TABLE public.prohibition RENAME CONSTRAINT uniq__caveat TO uniq__prohibition;
|
||||||
|
ALTER INDEX idx__public__caveat__user_id RENAME TO idx__public__prohibition__user_id;
|
||||||
|
ALTER INDEX idx__public__caveat__value RENAME TO idx__public__prohibition__value;
|
||||||
|
ALTER INDEX idx__public__caveat__created_ts RENAME TO idx__public__prohibition__created_ts;
|
||||||
|
ALTER INDEX idx__public__caveat__updated_ts RENAME TO idx__public__prohibition__updated_ts;
|
||||||
|
|
||||||
|
ALTER TABLE public.agreement_caveat RENAME TO agreement_prohibition;
|
||||||
|
ALTER TABLE public.agreement_prohibition RENAME CONSTRAINT uniq__agreement_caveat TO uniq__agreement_prohibition;
|
||||||
|
ALTER TABLE public.agreement_prohibition RENAME COLUMN caveat_id TO prohibition_id;
|
||||||
|
ALTER TABLE public.agreement_prohibition
|
||||||
|
DROP CONSTRAINT IF EXISTS agreement_caveat_caveat_id_fkey,
|
||||||
|
ADD CONSTRAINT agreement_prohibition_prohibition_id_fkey
|
||||||
|
FOREIGN KEY (prohibition_id)
|
||||||
|
REFERENCES public.prohibition(id);
|
||||||
|
ALTER INDEX idx__public__agreement_caveat__user_id RENAME TO idx__public__agreement_prohibition__user_id;
|
||||||
|
ALTER INDEX idx__public__agreement_caveat__agreement_id RENAME TO idx__public__agreement_prohibition__agreement_id;
|
||||||
|
ALTER INDEX idx__public__agreement_caveat__caveat_id RENAME TO idx__public__agreement_prohibition__prohibition_id;
|
||||||
|
ALTER INDEX idx__public__agreement_caveat__created_ts RENAME TO idx__public__agreement_prohibition__created_ts;
|
||||||
|
ALTER INDEX idx__public__agreement_caveat__updated_ts RENAME TO idx__public__agreement_prohibition__updated_ts;
|
||||||
|
|
||||||
|
ALTER TABLE agreement_content ADD COLUMN key VARCHAR(255) UNIQUE;
|
||||||
|
CREATE INDEX idx__agreement_content__key ON public.agreement_content (key);
|
||||||
|
ALTER TABLE agreement_content ADD COLUMN agreement_uuid UUID UNIQUE NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
|
||||||
|
|
||||||
|
ALTER TABLE purpose ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
ALTER TABLE prohibition ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
ALTER TABLE role ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
ALTER TABLE agreement_purpose ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
ALTER TABLE agreement_prohibition ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
ALTER TABLE agreement_role ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE public.purpose DROP CONSTRAINT uniq__purpose;
|
||||||
|
CREATE UNIQUE INDEX uniq__purpose ON public.purpose (COALESCE(user_id, -1), value);
|
||||||
|
ALTER TABLE public.prohibition DROP CONSTRAINT uniq__prohibition;
|
||||||
|
CREATE UNIQUE INDEX uniq__prohibition ON public.prohibition (COALESCE(user_id, -1), value);
|
||||||
|
ALTER TABLE public.role DROP CONSTRAINT uniq__role;
|
||||||
|
CREATE UNIQUE INDEX uniq__role ON public.role (COALESCE(user_id, -1), value);
|
||||||
|
ALTER TABLE public.agreement_purpose DROP CONSTRAINT uniq__agreement_purpose;
|
||||||
|
CREATE UNIQUE INDEX uniq__agreement_purpose ON public.agreement_purpose (COALESCE(user_id, -1), agreement_id, purpose_id);
|
||||||
|
ALTER TABLE public.agreement_prohibition DROP CONSTRAINT uniq__agreement_prohibition;
|
||||||
|
CREATE UNIQUE INDEX uniq__agreement_prohibition ON public.agreement_prohibition (COALESCE(user_id, -1), agreement_id, prohibition_id);
|
||||||
|
ALTER TABLE public.agreement_role DROP CONSTRAINT uniq__agreement_role;
|
||||||
|
CREATE UNIQUE INDEX uniq__agreement_role ON public.agreement_role (COALESCE(user_id, -1), agreement_id, role_id);
|
||||||
|
|
||||||
|
ALTER TABLE public.agreement ADD COLUMN context VARCHAR(255) DEFAULT 'https://protocol.jlinc.org/context/jlinc-v7.jsonld';
|
||||||
@@ -26,7 +26,40 @@ async function getAgreementContent(userId, hash) {
|
|||||||
} finally {
|
} finally {
|
||||||
await client.release();
|
await client.release();
|
||||||
}
|
}
|
||||||
return marked(content);
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAgreements(userId) {
|
||||||
|
let agreements = [];
|
||||||
|
const client = await getPool();
|
||||||
|
try {
|
||||||
|
let sql = `
|
||||||
|
SELECT
|
||||||
|
title,
|
||||||
|
hash
|
||||||
|
FROM agreement_content
|
||||||
|
WHERE user_id IS null
|
||||||
|
`;
|
||||||
|
let values = [];
|
||||||
|
if (userId) {
|
||||||
|
sql += `
|
||||||
|
OR user_id = $1
|
||||||
|
`;
|
||||||
|
values.push(userId);
|
||||||
|
}
|
||||||
|
sql += `
|
||||||
|
ORDER BY title ASC
|
||||||
|
`
|
||||||
|
const res = await client.query(sql, values);
|
||||||
|
if (res.rows.length > 0) {
|
||||||
|
agreements = res.rows;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
await client.release();
|
||||||
|
}
|
||||||
|
return agreements;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function routeAgreements(app) {
|
export function routeAgreements(app) {
|
||||||
@@ -34,14 +67,30 @@ export function routeAgreements(app) {
|
|||||||
app.get('/agreements/:hash', async (req, res) => {
|
app.get('/agreements/:hash', async (req, res) => {
|
||||||
const { hash } = req.params;
|
const { hash } = req.params;
|
||||||
const agreement = await getAgreementContent(null, hash);
|
const agreement = await getAgreementContent(null, hash);
|
||||||
res.send(agreement);
|
res.render('agreement', {
|
||||||
|
agreement: marked(agreement),
|
||||||
|
rawUrl: `/agreements/${hash}/raw`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/agreements/:hash/raw', async (req, res) => {
|
||||||
|
const { hash } = req.params;
|
||||||
|
const agreement = await getAgreementContent(null, hash);
|
||||||
|
res.send(`<pre>${agreement}</pre>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Route for /agreements/:userid/:hash
|
|
||||||
app.get('/agreements/:userId/:hash', async (req, res) => {
|
app.get('/agreements/:userId/:hash', async (req, res) => {
|
||||||
const { userId, hash } = req.params;
|
const { userId, hash } = req.params;
|
||||||
const agreement = await getAgreementContent(userId, hash);
|
const agreement = await getAgreementContent(userId, hash);
|
||||||
res.send(agreement);
|
res.render('agreement', {
|
||||||
|
agreement: marked(agreement),
|
||||||
|
rawUrl: `/agreements/${hash}/raw`,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/agreements/:userId/:hash/raw', async (req, res) => {
|
||||||
|
const { hash } = req.params;
|
||||||
|
const agreement = await getAgreementContent(userId, hash);
|
||||||
|
res.send(`<pre>${agreement}</pre>`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import bodyParser from "body-parser";
|
import bodyParser from "body-parser";
|
||||||
import { initModules, apiMiddleware } from "./auth.js";
|
import { initModules, apiMiddleware } from "./auth.js";
|
||||||
|
import { loadPep } from "../modules/pep/index.js";
|
||||||
import swaggerUi from "swagger-ui-express";
|
import swaggerUi from "swagger-ui-express";
|
||||||
import swaggerDocument from "./api/v1/swagger.json" assert { type: "json" };
|
import swaggerDocument from "./api/v1/swagger.json" with { type: "json" };
|
||||||
import { getConfig } from "../common/config.js";
|
import { getConfig } from "../common/config.js";
|
||||||
import { core } from "../modules/core/index.js"
|
import { core } from "../modules/core/index.js"
|
||||||
|
import { getAgreements } from "../http/agreements.js"
|
||||||
|
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import session from "express-session";
|
import session from "express-session";
|
||||||
@@ -34,8 +36,10 @@ async function renderPrivate(view, req, res, config) {
|
|||||||
const begin = new Date(now.getFullYear(), now.getMonth(), 1);
|
const begin = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
const usage = await getUsage(req.user, begin, end);
|
const usage = await getUsage(req.user, begin, end);
|
||||||
|
const agreements = await getAgreements(req?.user?.id);
|
||||||
res.render(view, {
|
res.render(view, {
|
||||||
config,
|
config,
|
||||||
|
agreements,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
usage,
|
usage,
|
||||||
});
|
});
|
||||||
@@ -80,12 +84,14 @@ export async function initHTTP(app) {
|
|||||||
logRequest(app);
|
logRequest(app);
|
||||||
routeAgreements(app);
|
routeAgreements(app);
|
||||||
|
|
||||||
|
await loadPep(app);
|
||||||
|
|
||||||
app.get("/", (req, res) => render('login', res, config));
|
app.get("/", (req, res) => render('login', res, config));
|
||||||
app.get("/dashboard", (req, res) => renderPrivate('dashboard', req, res, config));
|
app.get("/dashboard", (req, res) => renderPrivate('dashboard', req, res, config));
|
||||||
app.get("/refresh", refresh);
|
app.get("/refresh", refresh);
|
||||||
app.post('/logout', logout);
|
app.post('/logout', logout);
|
||||||
|
|
||||||
app.post("/api/v1/*", bodyParser.json(), apiMiddleware, core.post);
|
app.post(/^\/api\/v1\/.*$/, bodyParser.json(), apiMiddleware, core.post);
|
||||||
|
|
||||||
// app.use("/api/v1", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
// app.use("/api/v1", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/http/views/agreement.ejs
Normal file
21
src/http/views/agreement.ejs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<%- include('./include/header.ejs', { title: 'JLINC - MyTerms Agreement' }) %>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
color: #31A9BA !important
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="mdc-card" style="background: linear-gradient(333deg, rgb(0, 0, 0) 0%, rgb(79, 55, 139) 100%);">
|
||||||
|
|
||||||
|
<%- agreement %>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
View the <a href="<%- rawUrl %>">raw agreement content</a>.
|
||||||
|
|
||||||
|
|
||||||
|
<%- include('./include/footer.ejs') %>
|
||||||
@@ -12,4 +12,10 @@
|
|||||||
%>
|
%>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
<%-
|
||||||
|
include('./include/agreements.ejs', {
|
||||||
|
app: config.appModules['core']
|
||||||
|
})
|
||||||
|
%>
|
||||||
|
|
||||||
<%- include('./include/footer.ejs') %>
|
<%- include('./include/footer.ejs') %>
|
||||||
|
|||||||
26
src/http/views/include/agreements.ejs
Normal file
26
src/http/views/include/agreements.ejs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<% const cardStyle=`"background: linear-gradient(333deg, ${app.background.color1} 0%, ${app.background.color2} 100%);
|
||||||
|
margin-bottom: 30px;"`; const userApp=user.apps.find(ua=> app.type === 'core');
|
||||||
|
const buttonStyle = `"padding: 8px 10px 8px 10px; border-radius: 16px !important; background-color:
|
||||||
|
${app.button.color} !important; border: none !important;"`
|
||||||
|
%>
|
||||||
|
|
||||||
|
<div class="mdc-card mdc-theme--dark" style=<%- cardStyle %>>
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<div style="width: 30px">
|
||||||
|
<%- app.logo %>
|
||||||
|
</div>
|
||||||
|
<h2 style="margin-left: 10px; padding-bottom: 10px;">
|
||||||
|
Available Agreements
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<ul style="margin-top: 0px">
|
||||||
|
<% for (const agreement of agreements) { %>
|
||||||
|
<li style="padding-bottom: 1em"><a target="_new" class="agreement-link" href="/agreements/<%- agreement.hash %>"><%- agreement.title %></a></li>
|
||||||
|
<% } %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -64,6 +64,11 @@
|
|||||||
|
|
||||||
/* @include button.ink-color(#84565E); */
|
/* @include button.ink-color(#84565E); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agreement-link {
|
||||||
|
font-family: var(--md-ref-typeface-plain);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -109,9 +114,11 @@
|
|||||||
</script>
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 90%; max-width: 600px;">
|
<div style="width: 100%; overflow-y: auto; max-height: 100%; word-wrap: break-word;">
|
||||||
|
<center>
|
||||||
|
<div style="width: 90%; max-width: 600px; text-align: left">
|
||||||
<div
|
<div
|
||||||
style="display: flex; align-items: center; justify-content: center; gap: 0px; text-align: center; transform: translateX(-0px);">
|
style="padding-top: 20px; display: flex; align-items: center; justify-content: center; gap: 0px; text-align: center; transform: translateX(-0px);">
|
||||||
<div style="width: 200px; padding-bottom: 26px">
|
<div style="width: 200px; padding-bottom: 26px">
|
||||||
<%- include('./logo-white.svg') %>
|
<%- include('./logo-white.svg') %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<h3>Login with:</h3>
|
<h3>Login with:</h3>
|
||||||
<% for (const type in config.authModules) { %>
|
<% for (const type in config.authModules) { %>
|
||||||
|
<% if (type !== 'single') { %>
|
||||||
<div style="width: 100%; padding-bottom: 16px">
|
<div style="width: 100%; padding-bottom: 16px">
|
||||||
<button style="width: 100%" class="mdc-button mdc-button--raised mdc-button--leading" onclick="window.location.href='/login/<%= type %>'">
|
<button style="width: 100%" class="mdc-button mdc-button--raised mdc-button--leading" onclick="window.location.href='/login/<%= type %>'">
|
||||||
<span class="mdc-button__ripple"></span>
|
<span class="mdc-button__ripple"></span>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ const { JlincAgreement } = pkg;
|
|||||||
|
|
||||||
|
|
||||||
async function create(input) {
|
async function create(input) {
|
||||||
|
if (data.caveats) {
|
||||||
|
data.prohibitions = data.caveats;
|
||||||
|
delete data.caveats;
|
||||||
|
}
|
||||||
const data = await JlincAgreement.create(input);
|
const data = await JlincAgreement.create(input);
|
||||||
const message = data?.agreementId;
|
const message = data?.agreementId;
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import { getPool } from "../../../db/index.js";
|
|||||||
import { entity } from "./entity.js";
|
import { entity } from "./entity.js";
|
||||||
import { putQueue } from "../../../common/queue.js";
|
import { putQueue } from "../../../common/queue.js";
|
||||||
|
|
||||||
async function getAgreement(client, userId, id) {
|
async function getAgreement(client, userId, id, key) {
|
||||||
|
const whereClause = id ? `a.agreement_id_uuid = $1` : `a.agreement_id_uuid = (SELECT agreement_uuid FROM agreement_content WHERE lower(key) = lower($1))`;
|
||||||
|
const whereValue = id ? id : key;
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT
|
SELECT
|
||||||
JSON_BUILD_OBJECT(
|
JSON_BUILD_OBJECT(
|
||||||
'version', a.version,
|
'version', a.version,
|
||||||
|
'@context', a.context,
|
||||||
'parent', a.parent,
|
'parent', a.parent,
|
||||||
'agreementId', a.agreement_id_uuid,
|
'agreementId', a.agreement_id_uuid,
|
||||||
'created', a.created,
|
'created', a.created,
|
||||||
@@ -20,41 +23,58 @@ async function getAgreement(client, userId, id) {
|
|||||||
'purposes', (
|
'purposes', (
|
||||||
SELECT JSON_AGG(p.value ORDER BY p.value)
|
SELECT JSON_AGG(p.value ORDER BY p.value)
|
||||||
FROM purpose p
|
FROM purpose p
|
||||||
LEFT JOIN agreement_purpose ap
|
INNER JOIN agreement_purpose ap
|
||||||
ON p.id = ap.purpose_id
|
ON p.id = ap.purpose_id
|
||||||
AND ap.agreement_id = a.id
|
AND ap.agreement_id = a.id
|
||||||
AND p.user_id = a.user_id
|
AND (
|
||||||
AND ap.user_id = a.user_id
|
(p.user_id = a.user_id AND ap.user_id = a.user_id)
|
||||||
|
OR
|
||||||
|
(p.user_id IS NULL AND ap.user_id IS NULL AND a.user_id IS NULL)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
'caveats', (
|
'prohibitions', (
|
||||||
SELECT JSON_AGG(c.value ORDER BY c.value)
|
SELECT JSON_AGG(c.value ORDER BY c.value)
|
||||||
FROM caveat c
|
FROM prohibition c
|
||||||
LEFT JOIN agreement_caveat ac
|
INNER JOIN agreement_prohibition ac
|
||||||
ON c.id = ac.caveat_id
|
ON c.id = ac.prohibition_id
|
||||||
AND ac.agreement_id = a.id
|
AND ac.agreement_id = a.id
|
||||||
AND c.user_id = a.user_id
|
AND (
|
||||||
AND ac.user_id = a.user_id
|
(c.user_id = ac.user_id AND ac.user_id = a.user_id)
|
||||||
|
OR
|
||||||
|
(c.user_id IS NULL AND ac.user_id IS NULL AND a.user_id IS NULL)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
'validRoles', (
|
'validRoles', (
|
||||||
SELECT JSON_AGG(r.value ORDER BY r.value)
|
SELECT JSON_AGG(r.value ORDER BY r.value)
|
||||||
FROM role r
|
FROM role r
|
||||||
LEFT JOIN agreement_role ar
|
INNER JOIN agreement_role ar
|
||||||
ON r.id = ar.role_id
|
ON r.id = ar.role_id
|
||||||
AND ar.agreement_id = a.id
|
AND ar.agreement_id = a.id
|
||||||
AND r.user_id = a.user_id
|
AND (
|
||||||
AND ar.user_id = a.user_id
|
(r.user_id = ar.user_id AND ar.user_id = a.user_id)
|
||||||
|
OR
|
||||||
|
(r.user_id IS NULL AND ar.user_id IS NULL AND a.user_id IS NULL)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
) AS record
|
) AS record
|
||||||
FROM agreement a
|
FROM agreement a
|
||||||
WHERE a.agreement_id_uuid = $1
|
WHERE ${whereClause}
|
||||||
AND a.user_id = $2;
|
AND (
|
||||||
|
a.user_id = $2
|
||||||
|
OR a.user_id IS NULL
|
||||||
|
)
|
||||||
`
|
`
|
||||||
const res = await client.query(sql, [
|
const res = await client.query(sql, [
|
||||||
id,
|
whereValue,
|
||||||
userId,
|
userId,
|
||||||
]);
|
]);
|
||||||
if (res.rows.length > 0 && res.rows[0].record) {
|
if (res.rows.length > 0 && res.rows[0].record) {
|
||||||
return res.rows[0].record;
|
const ret = res.rows[0].record;
|
||||||
|
if (!ret.ids) ret.ids = [];
|
||||||
|
if (!ret.purposes) ret.purposes = [];
|
||||||
|
if (!ret.prohibitions) ret.prohibitions = [];
|
||||||
|
if (!ret.validRoles) ret.validRoles = [];
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -99,7 +119,7 @@ async function get(input, userId) {
|
|||||||
};
|
};
|
||||||
const client = await getPool();
|
const client = await getPool();
|
||||||
try {
|
try {
|
||||||
const existingAgreement = await getAgreement(client, userId, input.agreementId)
|
const existingAgreement = await getAgreement(client, userId, input.agreementId, input.key)
|
||||||
if (!existingAgreement) {
|
if (!existingAgreement) {
|
||||||
response.error = 'agreement not found'
|
response.error = 'agreement not found'
|
||||||
} else {
|
} else {
|
||||||
@@ -135,6 +155,7 @@ async function save(client, userId, agreement) {
|
|||||||
version,
|
version,
|
||||||
parent,
|
parent,
|
||||||
agreement_id_uuid,
|
agreement_id_uuid,
|
||||||
|
context,
|
||||||
created,
|
created,
|
||||||
created_as_ts
|
created_as_ts
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@@ -143,13 +164,15 @@ async function save(client, userId, agreement) {
|
|||||||
$3,
|
$3,
|
||||||
$4,
|
$4,
|
||||||
$5,
|
$5,
|
||||||
$6
|
$6,
|
||||||
|
$7
|
||||||
) RETURNING id;
|
) RETURNING id;
|
||||||
`, [
|
`, [
|
||||||
userId,
|
userId,
|
||||||
agreement.version,
|
agreement.version,
|
||||||
agreement.parent,
|
agreement.parent,
|
||||||
agreement.agreementId,
|
agreement.agreementId,
|
||||||
|
agreement["@context"],
|
||||||
agreement.created,
|
agreement.created,
|
||||||
new Date(agreement.created).toISOString(),
|
new Date(agreement.created).toISOString(),
|
||||||
]);
|
]);
|
||||||
@@ -195,7 +218,7 @@ async function save(client, userId, agreement) {
|
|||||||
SELECT id
|
SELECT id
|
||||||
FROM purpose
|
FROM purpose
|
||||||
WHERE value = $3
|
WHERE value = $3
|
||||||
AND user_id = $1
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
`, [
|
`, [
|
||||||
@@ -204,9 +227,9 @@ async function save(client, userId, agreement) {
|
|||||||
purpose,
|
purpose,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
for (const caveat of agreement.caveats) {
|
for (const prohibition of agreement.prohibitions) {
|
||||||
await client.query(`
|
await client.query(`
|
||||||
INSERT INTO caveat (
|
INSERT INTO prohibition (
|
||||||
user_id,
|
user_id,
|
||||||
value
|
value
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@@ -215,27 +238,27 @@ async function save(client, userId, agreement) {
|
|||||||
) ON CONFLICT DO NOTHING;
|
) ON CONFLICT DO NOTHING;
|
||||||
`, [
|
`, [
|
||||||
userId,
|
userId,
|
||||||
caveat,
|
prohibition,
|
||||||
]);
|
]);
|
||||||
await client.query(`
|
await client.query(`
|
||||||
INSERT INTO agreement_caveat (
|
INSERT INTO agreement_prohibition (
|
||||||
user_id,
|
user_id,
|
||||||
agreement_id,
|
agreement_id,
|
||||||
caveat_id
|
prohibition_id
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
(
|
(
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM caveat
|
FROM prohibition
|
||||||
WHERE value = $3
|
WHERE value = $3
|
||||||
AND user_id = $1
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
`, [
|
`, [
|
||||||
userId,
|
userId,
|
||||||
res.rows[0].id,
|
res.rows[0].id,
|
||||||
caveat,
|
prohibition,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
for (const role of agreement.validRoles) {
|
for (const role of agreement.validRoles) {
|
||||||
@@ -263,7 +286,7 @@ async function save(client, userId, agreement) {
|
|||||||
SELECT id
|
SELECT id
|
||||||
FROM role
|
FROM role
|
||||||
WHERE value = $3
|
WHERE value = $3
|
||||||
AND user_id = $1
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
`, [
|
`, [
|
||||||
@@ -340,6 +363,10 @@ async function create(input, userId, _client, uuid) {
|
|||||||
input.didDocs.push((await entity.getEntity(client, userId, shortName)).didDoc)
|
input.didDocs.push((await entity.getEntity(client, userId, shortName)).didDoc)
|
||||||
}
|
}
|
||||||
delete input.shortNames;
|
delete input.shortNames;
|
||||||
|
if (input.caveats) {
|
||||||
|
input.prohibitions = input.caveats;
|
||||||
|
delete input.caveats;
|
||||||
|
}
|
||||||
const agreement = await JlincAgreement.create(input);
|
const agreement = await JlincAgreement.create(input);
|
||||||
if (uuid) {
|
if (uuid) {
|
||||||
agreement.agreementId = uuid;
|
agreement.agreementId = uuid;
|
||||||
|
|||||||
@@ -6,145 +6,165 @@ import { data } from "./data/index.js";
|
|||||||
import { archive } from "./archive.js";
|
import { archive } from "./archive.js";
|
||||||
import { trackUsage } from "./usage.js";
|
import { trackUsage } from "./usage.js";
|
||||||
import { getConfig } from "../../common/config.js";
|
import { getConfig } from "../../common/config.js";
|
||||||
|
import { evaluate } from "../pep/index.js";
|
||||||
|
|
||||||
async function post(req, res) {
|
async function post(req, res) {
|
||||||
let response = {
|
let response = {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Unknown error',
|
error: 'Unknown error',
|
||||||
};
|
};
|
||||||
|
let errorCode = 400;
|
||||||
let type;
|
let type;
|
||||||
const prefix = `${req.method} ${req.url}`;
|
const prefix = `${req.method} ${req.url}`;
|
||||||
|
let evaluation = false;
|
||||||
try {
|
try {
|
||||||
const input = req.body;
|
const input = req.body;
|
||||||
|
if (input.auth) {
|
||||||
|
evaluation = await evaluate(input.auth);
|
||||||
|
} else {
|
||||||
|
evaluation = true;
|
||||||
|
}
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
if (Object.keys(config.appModules).includes('core')) {
|
if (Object.keys(config.appModules).includes('core')) {
|
||||||
switch (req.url) {
|
switch (req.url) {
|
||||||
|
case '/api/v1/auth':
|
||||||
|
evaluation = await evaluate(input);
|
||||||
|
if (evaluation)
|
||||||
|
response = {
|
||||||
|
data: {
|
||||||
|
decision: true
|
||||||
|
},
|
||||||
|
message: 'Authorization allowed'
|
||||||
|
};
|
||||||
|
type = 'core';
|
||||||
|
break;
|
||||||
case '/api/v1/did/create':
|
case '/api/v1/did/create':
|
||||||
response = await did.create(input);
|
if (evaluation) response = await did.create(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/did/rotate':
|
case '/api/v1/did/rotate':
|
||||||
response = await did.rotate(input);
|
if (evaluation) response = await did.rotate(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/did/updateServices':
|
case '/api/v1/did/updateServices':
|
||||||
response = await did.updateServices(input);
|
if (evaluation) response = await did.updateServices(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/did/send':
|
case '/api/v1/did/send':
|
||||||
response = await did.send(input);
|
if (evaluation) response = await did.send(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/did/resolve':
|
case '/api/v1/did/resolve':
|
||||||
response = await did.resolve(input);
|
if (evaluation) response = await did.resolve(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/agreement/create':
|
case '/api/v1/agreement/create':
|
||||||
response = await agreement.create(input);
|
if (evaluation) response = await agreement.create(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/agreement/sign':
|
case '/api/v1/agreement/sign':
|
||||||
response = await agreement.sign(input);
|
if (evaluation) response = await agreement.sign(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/agreement/send':
|
case '/api/v1/agreement/send':
|
||||||
response = await agreement.send(input);
|
if (evaluation) response = await agreement.send(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/event/create':
|
case '/api/v1/event/create':
|
||||||
response = await event.create(input);
|
if (evaluation) response = await event.create(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/event/sign':
|
case '/api/v1/event/sign':
|
||||||
response = await event.sign(input);
|
if (evaluation) response = await event.sign(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/event/send':
|
case '/api/v1/event/send':
|
||||||
response = await event.send(input);
|
if (evaluation) response = await event.send(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/audit/create':
|
case '/api/v1/audit/create':
|
||||||
response = await audit.create(input);
|
if (evaluation) response = await audit.create(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/audit/sign':
|
case '/api/v1/audit/sign':
|
||||||
response = await audit.sign(input);
|
if (evaluation) response = await audit.sign(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/audit/send':
|
case '/api/v1/audit/send':
|
||||||
response = await audit.send(input);
|
if (evaluation) response = await audit.send(input);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/entity/get':
|
case '/api/v1/data/entity/get':
|
||||||
response = await data.entity.get(input, req.session.user_id);
|
if (evaluation) response = await data.entity.get(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/entity/domains/get':
|
case '/api/v1/data/entity/domains/get':
|
||||||
response = await data.entity.getDomains(input, req.session.user_id);
|
if (evaluation) response = await data.entity.getDomains(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/entity/create':
|
case '/api/v1/data/entity/create':
|
||||||
response = await data.entity.create(input, req.session.user_id);
|
if (evaluation) response = await data.entity.create(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/agreement/get':
|
case '/api/v1/data/agreement/get':
|
||||||
response = await data.agreement.get(input, req.session.user_id);
|
if (evaluation) response = await data.agreement.get(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/agreement/create':
|
case '/api/v1/data/agreement/create':
|
||||||
response = await data.agreement.create(input, req.session.user_id);
|
if (evaluation) response = await data.agreement.create(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/agreement/process':
|
case '/api/v1/data/agreement/process':
|
||||||
response = await data.agreement.process(input, req.session.user_id);
|
if (evaluation) response = await data.agreement.process(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/agreement/produce':
|
case '/api/v1/data/agreement/produce':
|
||||||
response = await data.agreement.produce(input, req.session.user_id);
|
if (evaluation) response = await data.agreement.produce(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/event/get':
|
case '/api/v1/data/event/get':
|
||||||
response = await data.event.get(input, req.session.user_id);
|
if (evaluation) response = await data.event.get(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/event/create':
|
case '/api/v1/data/event/create':
|
||||||
response = await data.event.create(input, req.session.user_id);
|
if (evaluation) response = await data.event.create(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/event/process':
|
case '/api/v1/data/event/process':
|
||||||
response = await data.event.process(input, req.session.user_id);
|
if (evaluation) response = await data.event.process(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/event/produce':
|
case '/api/v1/data/event/produce':
|
||||||
response = await data.event.produce(input, req.session.user_id);
|
if (evaluation) response = await data.event.produce(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/data/audit/verify':
|
case '/api/v1/data/audit/verify':
|
||||||
response = await data.audit.verify(input, req.session.user_id);
|
if (evaluation) response = await data.audit.verify(input, req.session.user_id);
|
||||||
type = 'core';
|
type = 'core';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (Object.keys(config.appModules).includes('archive')) {
|
if (Object.keys(config.appModules).includes('archive')) {
|
||||||
switch (req.url) {
|
switch (req.url) {
|
||||||
case '/api/v1/audit/put':
|
case '/api/v1/audit/put':
|
||||||
response = await archive.put(input);
|
if (evaluation) response = await archive.put(input);
|
||||||
type = 'archive';
|
type = 'archive';
|
||||||
break;
|
break;
|
||||||
case '/api/v1/audit/get':
|
case '/api/v1/audit/get':
|
||||||
response = await archive.get(input);
|
if (evaluation) response = await archive.get(input);
|
||||||
type = 'archive';
|
type = 'archive';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!type) {
|
if (!type) {
|
||||||
response.error = 'Page not found';
|
response.error = 'Page not found';
|
||||||
res.status(404).send(response);
|
errorCode = 404;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
response.error = e.message;
|
response.error = e.message;
|
||||||
} finally {
|
} finally {
|
||||||
|
if (evaluation) {
|
||||||
if (response?.message) {
|
if (response?.message) {
|
||||||
req.apiMessage = response.message;
|
req.apiMessage = response.message;
|
||||||
} else if (response?.data?.error) {
|
} else if (response?.data?.error) {
|
||||||
@@ -154,8 +174,18 @@ async function post(req, res) {
|
|||||||
}
|
}
|
||||||
if (response?.data)
|
if (response?.data)
|
||||||
response = response.data;
|
response = response.data;
|
||||||
|
res.status(response?.error ? errorCode : 200).json(response);
|
||||||
|
} else {
|
||||||
|
response = {
|
||||||
|
data: {
|
||||||
|
decision: false
|
||||||
|
},
|
||||||
|
message: 'Authorization denied'
|
||||||
|
}
|
||||||
|
req.apiMessage = response.message;
|
||||||
|
res.status(200).json(response.data);
|
||||||
|
}
|
||||||
await trackUsage(req.session.user_id, req.url, type, response?.error ? false : true);
|
await trackUsage(req.session.user_id, req.url, type, response?.error ? false : true);
|
||||||
res.status(response?.error ? 400 : 200).json(response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
src/modules/pep/cerbos.js
Normal file
63
src/modules/pep/cerbos.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { getConfig } from "../../common/config.js";
|
||||||
|
import axios from 'axios';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
export async function init() {
|
||||||
|
const config = getConfig();
|
||||||
|
// No initialization required
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function check(req) {
|
||||||
|
// Ref:
|
||||||
|
// ----
|
||||||
|
// curl -X POST http://localhost:3592/api/check/resources \
|
||||||
|
// -H "Content-Type: application/json" \
|
||||||
|
// -d '{
|
||||||
|
// "requestId": "test-check-1",
|
||||||
|
// "principal": {
|
||||||
|
// "id": "user123",
|
||||||
|
// "roles": ["user"]
|
||||||
|
// },
|
||||||
|
// "resources": [
|
||||||
|
// {
|
||||||
|
// "resource": {
|
||||||
|
// "kind": "privateData",
|
||||||
|
// "id": "record001",
|
||||||
|
// "attr": {}
|
||||||
|
// },
|
||||||
|
// "actions": ["read"]
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }'
|
||||||
|
|
||||||
|
const r = {
|
||||||
|
requestId: uuidv4(),
|
||||||
|
principal: {
|
||||||
|
id: req.subject.id,
|
||||||
|
roles: [req.subject.type]
|
||||||
|
},
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
resource: {
|
||||||
|
kind: req.resource.type,
|
||||||
|
id: req.resource.id,
|
||||||
|
attr: {}
|
||||||
|
},
|
||||||
|
actions: [req.action.name]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (req.resource.properties.ownerID) {
|
||||||
|
r.resources[0].resource.attr = { owner: req.resource.properties.ownerID }
|
||||||
|
}
|
||||||
|
const config = getConfig();
|
||||||
|
const result = await axios.post(
|
||||||
|
`${config.pdpUrl}/api/check/resources`,
|
||||||
|
r,
|
||||||
|
)
|
||||||
|
console.log(`Auth check: ${JSON.stringify(result.data)}`);
|
||||||
|
if (result.data?.results[0]?.actions[req.action.name] === 'EFFECT_ALLOW') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
35
src/modules/pep/index.js
Normal file
35
src/modules/pep/index.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { getConfig } from "../../common/config.js";
|
||||||
|
|
||||||
|
let evaluator;
|
||||||
|
|
||||||
|
export async function loadPep(app) {
|
||||||
|
const config = getConfig();
|
||||||
|
if (config.pdpType) {
|
||||||
|
const pepModulePath = `./${config.pdpType}.js`;
|
||||||
|
const { init, check } = await import(pepModulePath);
|
||||||
|
await init();
|
||||||
|
|
||||||
|
// // Follow AuthZEN base format
|
||||||
|
// // {
|
||||||
|
// // subject: {
|
||||||
|
// // type: "user",
|
||||||
|
// // id: "1abc",
|
||||||
|
// // },
|
||||||
|
// // action: {
|
||||||
|
// // name: "read"
|
||||||
|
// // },
|
||||||
|
// // resource: {
|
||||||
|
// // type: "data",
|
||||||
|
// // id: "2def",
|
||||||
|
// // properties: {
|
||||||
|
// // ownerID: "my@me.com"
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
evaluator = check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function evaluate(req) {
|
||||||
|
return await evaluator(req);
|
||||||
|
}
|
||||||
9645
src/package-lock.json
generated
9645
src/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,31 +20,32 @@
|
|||||||
"license": "SSPLv1",
|
"license": "SSPLv1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jlinc/core": "file:./packages/core",
|
"@jlinc/core": "file:./packages/core",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.13.1",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^2.2.0",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.21.2",
|
"express": "^5.1.0",
|
||||||
"express-session": "^1.18.1",
|
"express-session": "^1.18.2",
|
||||||
"marked": "^16.1.1",
|
"marked": "^16.4.1",
|
||||||
"memorystore": "^1.6.7",
|
"memorystore": "^1.6.7",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-openidconnect": "^0.1.2",
|
"passport-openidconnect": "^0.1.2",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.16.3",
|
||||||
"safe-stable-stringify": "^2.5.0",
|
"safe-stable-stringify": "^2.5.0",
|
||||||
"sodium-native": "^5.0.6",
|
"sodium-native": "^5.0.9",
|
||||||
"swagger-ui-express": "^5.0.1"
|
"swagger-ui-express": "^5.0.1",
|
||||||
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.25.9",
|
"@babel/eslint-parser": "^7.28.5",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.26.0",
|
"@babel/plugin-syntax-import-assertions": "^7.27.1",
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.39.1",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"globals": "^15.14.0",
|
"globals": "^16.5.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^30.2.0",
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.10",
|
||||||
"prettier": "^3.4.2"
|
"prettier": "^3.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user