jinaga-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/authentication/authentication-device.d.ts +20 -0
- package/dist/authentication/authentication-device.js +61 -0
- package/dist/authentication/authentication-device.js.map +1 -0
- package/dist/authentication/authentication-impl.d.ts +21 -0
- package/dist/authentication/authentication-impl.js +61 -0
- package/dist/authentication/authentication-impl.js.map +1 -0
- package/dist/authentication/authentication-session.d.ts +23 -0
- package/dist/authentication/authentication-session.js +85 -0
- package/dist/authentication/authentication-session.js.map +1 -0
- package/dist/authentication/authentication.d.ts +5 -0
- package/dist/authentication/authentication.js +3 -0
- package/dist/authentication/authentication.js.map +1 -0
- package/dist/authentication.d.ts +17 -0
- package/dist/authentication.js +52 -0
- package/dist/authentication.js.map +1 -0
- package/dist/authorization/authorization-keystore.d.ts +13 -0
- package/dist/authorization/authorization-keystore.js +61 -0
- package/dist/authorization/authorization-keystore.js.map +1 -0
- package/dist/http/node-http.d.ts +7 -0
- package/dist/http/node-http.js +126 -0
- package/dist/http/node-http.js.map +1 -0
- package/dist/http/router.d.ts +20 -0
- package/dist/http/router.js +223 -0
- package/dist/http/router.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/jinaga-server.d.ts +18 -0
- package/dist/jinaga-server.js +101 -0
- package/dist/jinaga-server.js.map +1 -0
- package/dist/keystore.d.ts +9 -0
- package/dist/keystore.js +3 -0
- package/dist/keystore.js.map +1 -0
- package/dist/memory/memory-keystore.d.ts +16 -0
- package/dist/memory/memory-keystore.js +86 -0
- package/dist/memory/memory-keystore.js.map +1 -0
- package/dist/postgres/connection.d.ts +12 -0
- package/dist/postgres/connection.js +83 -0
- package/dist/postgres/connection.js.map +1 -0
- package/dist/postgres/edge-record.d.ts +9 -0
- package/dist/postgres/edge-record.js +30 -0
- package/dist/postgres/edge-record.js.map +1 -0
- package/dist/postgres/maps.d.ts +21 -0
- package/dist/postgres/maps.js +86 -0
- package/dist/postgres/maps.js.map +1 -0
- package/dist/postgres/postgres-keystore.d.ts +20 -0
- package/dist/postgres/postgres-keystore.js +168 -0
- package/dist/postgres/postgres-keystore.js.map +1 -0
- package/dist/postgres/postgres-store.d.ts +20 -0
- package/dist/postgres/postgres-store.js +717 -0
- package/dist/postgres/postgres-store.js.map +1 -0
- package/dist/postgres/query-description-builder.d.ts +13 -0
- package/dist/postgres/query-description-builder.js +104 -0
- package/dist/postgres/query-description-builder.js.map +1 -0
- package/dist/postgres/query-description.d.ts +71 -0
- package/dist/postgres/query-description.js +256 -0
- package/dist/postgres/query-description.js.map +1 -0
- package/dist/postgres/specification-result-sql.d.ts +41 -0
- package/dist/postgres/specification-result-sql.js +260 -0
- package/dist/postgres/specification-result-sql.js.map +1 -0
- package/dist/postgres/specification-sql.d.ts +3 -0
- package/dist/postgres/specification-sql.js +119 -0
- package/dist/postgres/specification-sql.js.map +1 -0
- package/dist/postgres/sql.d.ts +10 -0
- package/dist/postgres/sql.js +396 -0
- package/dist/postgres/sql.js.map +1 -0
- package/dist/util/fn.d.ts +6 -0
- package/dist/util/fn.js +81 -0
- package/dist/util/fn.js.map +1 -0
- package/dist/util/promise.d.ts +1 -0
- package/dist/util/promise.js +10 -0
- package/dist/util/promise.js.map +1 -0
- package/docs/registrations.sql +22 -0
- package/docs/specification.txt +41 -0
- package/docs/sql-generator.gv +25 -0
- package/integration-test/docker-compose.yaml +13 -0
- package/integration-test/jinaga-test/Dockerfile +12 -0
- package/integration-test/jinaga-test/jinaga.test.js +425 -0
- package/integration-test/jinaga-test/package-lock.json +6767 -0
- package/integration-test/jinaga-test/package.json +25 -0
- package/integration-test/postgres/0-init-app-db.sh +16 -0
- package/integration-test/postgres/1-setup.sql +219 -0
- package/integration-test/postgres/2-grant.sh +17 -0
- package/integration-test/postgres/Dockerfile +5 -0
- package/integration-test/run.sh +17 -0
- package/integration-test/start_db.sh +2 -0
- package/integration-test/stop_db.sh +3 -0
- package/integration-test/test.sh +8 -0
- package/interactive/index.js +56 -0
- package/jest.config.js +12 -0
- package/package.json +51 -0
- package/scripts/combined.sql +46 -0
- package/scripts/cte.sql +55 -0
- package/scripts/separate.sql +27 -0
- package/setup.sh +34 -0
- package/setup.sql +329 -0
- package/src/authentication/authentication-device.ts +68 -0
- package/src/authentication/authentication-impl.ts +59 -0
- package/src/authentication/authentication-session.ts +95 -0
- package/src/authentication/authentication.ts +6 -0
- package/src/authentication.ts +45 -0
- package/src/authorization/authorization-keystore.ts +67 -0
- package/src/http/node-http.ts +106 -0
- package/src/http/router.ts +237 -0
- package/src/index.ts +1 -0
- package/src/jinaga-server.ts +125 -0
- package/src/keystore.ts +10 -0
- package/src/memory/memory-keystore.ts +93 -0
- package/src/postgres/connection.ts +73 -0
- package/src/postgres/edge-record.ts +36 -0
- package/src/postgres/maps.ts +89 -0
- package/src/postgres/postgres-keystore.ts +167 -0
- package/src/postgres/postgres-store.ts +874 -0
- package/src/postgres/query-description-builder.ts +116 -0
- package/src/postgres/query-description.ts +385 -0
- package/src/postgres/specification-result-sql.ts +362 -0
- package/src/postgres/specification-sql.ts +138 -0
- package/src/postgres/sql.ts +482 -0
- package/src/util/fn.ts +63 -0
- package/src/util/promise.ts +5 -0
- package/test/authorization/authorizationSpec.ts +209 -0
- package/test/postgres/postgresSpec.ts +385 -0
- package/test/postgres/postgresSpecificationSpec.ts +742 -0
- package/test/reporter.js +25 -0
- package/tsconfig.json +25 -0
- package/upgrade.sh +21 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export type FactTypeMap = Map<string, number>;
|
|
2
|
+
|
|
3
|
+
export function emptyFactTypeMap() {
|
|
4
|
+
return new Map<string, number>();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function copyFactTypeMap(map: FactTypeMap) {
|
|
8
|
+
return new Map<string, number>(map);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function addFactType(map: FactTypeMap, name: string, fact_type_id: number) {
|
|
12
|
+
return map.set(name, fact_type_id);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getFactTypeId(map: FactTypeMap, name: string) {
|
|
16
|
+
return map.get(name);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function mergeFactTypes(map1: FactTypeMap, map2: FactTypeMap) {
|
|
20
|
+
return new Map<string, number>([...map1, ...map2]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type RoleMap = Map<number, Map<string, number>>;
|
|
24
|
+
|
|
25
|
+
export function emptyRoleMap() {
|
|
26
|
+
return new Map<number, Map<string, number>>();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function copyRoleMap(map: RoleMap) {
|
|
30
|
+
return new Map<number, Map<string, number>>(map);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function addRole(map: RoleMap, defining_fact_type_id: number, name: string, role_id: number) {
|
|
34
|
+
const factTypeRoles = map.get(defining_fact_type_id) || new Map<string, number>();
|
|
35
|
+
const modifiedFactTypeRoles = factTypeRoles.set(name, role_id);
|
|
36
|
+
return map.set(defining_fact_type_id, modifiedFactTypeRoles);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function hasRole(map: RoleMap, defining_fact_type_id: number, name: string) {
|
|
40
|
+
const roleMap = map.get(defining_fact_type_id);
|
|
41
|
+
return roleMap && roleMap.has(name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getRoleId(map: RoleMap, defining_fact_type_id: number, name: string) {
|
|
45
|
+
const roleMap = map.get(defining_fact_type_id);
|
|
46
|
+
return roleMap && roleMap.get(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function mergeRoleMaps(map1: RoleMap, map2: RoleMap) {
|
|
50
|
+
let merged = new Map<number, Map<string, number>>(map1);
|
|
51
|
+
for (const [defining_fact_type_id, roleMap] of map2) {
|
|
52
|
+
const mergedRoleMap = merged.get(defining_fact_type_id) || new Map<string, number>();
|
|
53
|
+
const mergedFactTypeRoles = new Map<string, number>([...mergedRoleMap, ...roleMap]);
|
|
54
|
+
merged = merged.set(defining_fact_type_id, mergedFactTypeRoles);
|
|
55
|
+
}
|
|
56
|
+
return merged;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type FactMap = Map<string, Map<number, number>>;
|
|
60
|
+
|
|
61
|
+
export function emptyFactMap() {
|
|
62
|
+
return new Map<string, Map<number, number>>();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function addFact(map: FactMap, hash: string, fact_type_id: number, fact_id: number) {
|
|
66
|
+
const typeMap = map.get(hash) || new Map<number, number>();
|
|
67
|
+
const modifiedTypeMap = typeMap.set(fact_type_id, fact_id);
|
|
68
|
+
return map.set(hash, modifiedTypeMap);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function hasFact(map: FactMap, hash: string, fact_type_id: number) {
|
|
72
|
+
const typeMap = map.get(hash);
|
|
73
|
+
return typeMap && typeMap.has(fact_type_id);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getFactId(map: FactMap, hash: string, fact_type_id: number) {
|
|
77
|
+
const typeMap = map.get(hash) || new Map<number, number>();
|
|
78
|
+
return typeMap.get(fact_type_id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type PublicKeyMap = Map<string, number>;
|
|
82
|
+
|
|
83
|
+
export function emptyPublicKeyMap() {
|
|
84
|
+
return new Map<string, number>();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getPublicKeyId(publicKeyMap: PublicKeyMap, publicKey: string) {
|
|
88
|
+
return publicKeyMap.get(publicKey);
|
|
89
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { canonicalizeFact, computeHash, FactEnvelope, FactRecord, PredecessorCollection, Trace, UserIdentity } from "jinaga";
|
|
2
|
+
import { md, pki, util } from "node-forge";
|
|
3
|
+
import { PoolClient } from "pg";
|
|
4
|
+
|
|
5
|
+
import { Keystore } from "../keystore";
|
|
6
|
+
import { ConnectionFactory } from "./connection";
|
|
7
|
+
|
|
8
|
+
interface KeyPair {
|
|
9
|
+
publicPem: string;
|
|
10
|
+
privatePem: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class PostgresKeystore implements Keystore {
|
|
14
|
+
private connectionFactory: ConnectionFactory;
|
|
15
|
+
private cache: Map<string, KeyPair> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor (postgresUri: string) {
|
|
18
|
+
this.connectionFactory = new ConnectionFactory(postgresUri);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async close() {
|
|
22
|
+
await this.connectionFactory.close();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getOrCreateUserFact(userIdentity: UserIdentity): Promise<FactRecord> {
|
|
26
|
+
return this.getOrCreateIdentityFact('Jinaga.User', userIdentity);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getOrCreateDeviceFact(deviceIdentity: UserIdentity): Promise<FactRecord> {
|
|
30
|
+
return this.getOrCreateIdentityFact('Jinaga.Device', deviceIdentity);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getUserFact(userIdentity: UserIdentity): Promise<FactRecord> {
|
|
34
|
+
return this.getIdentityFact('Jinaga.User', userIdentity);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getDeviceFact(deviceIdentity: UserIdentity): Promise<FactRecord> {
|
|
38
|
+
return this.getIdentityFact('Jinaga.Device', deviceIdentity);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async signFacts(userIdentity: UserIdentity, facts: FactRecord[]): Promise<FactEnvelope[]> {
|
|
42
|
+
if (!userIdentity) {
|
|
43
|
+
return facts.map(fact => ({ fact, signatures: [] }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { publicPem, privatePem } = await this.getKeyPair(userIdentity);
|
|
47
|
+
const privateKey = <pki.rsa.PrivateKey>pki.privateKeyFromPem(privatePem);
|
|
48
|
+
const envelopes: FactEnvelope[] = facts.map(fact => signFact(fact, publicPem, privateKey));
|
|
49
|
+
return envelopes;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async getOrCreateIdentityFact(type: string, identity: UserIdentity): Promise<FactRecord> {
|
|
53
|
+
if (!identity) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const { publicPem } = await this.getOrGenerateKeyPair(identity);
|
|
57
|
+
const predecessors: PredecessorCollection = {};
|
|
58
|
+
const fields = {
|
|
59
|
+
publicKey: publicPem
|
|
60
|
+
};
|
|
61
|
+
const hash = computeHash(fields, predecessors);
|
|
62
|
+
return { type, hash, predecessors, fields };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async getIdentityFact(type: string, identity: UserIdentity): Promise<FactRecord> {
|
|
66
|
+
if (!identity) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const { publicPem } = await this.getKeyPair(identity);
|
|
70
|
+
const predecessors: PredecessorCollection = {};
|
|
71
|
+
const fields = {
|
|
72
|
+
publicKey: publicPem
|
|
73
|
+
};
|
|
74
|
+
const hash = computeHash(fields, predecessors);
|
|
75
|
+
return { type, hash, predecessors, fields };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async getKeyPair(userIdentity: UserIdentity): Promise<KeyPair> {
|
|
79
|
+
const key = getUserIdentityKey(userIdentity);
|
|
80
|
+
if (this.cache.has(key)) {
|
|
81
|
+
return this.cache.get(key);
|
|
82
|
+
}
|
|
83
|
+
const keyPair = await this.connectionFactory.with(connection =>
|
|
84
|
+
this.selectKeyPair(connection, userIdentity));
|
|
85
|
+
this.cache.set(key, keyPair);
|
|
86
|
+
return keyPair;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async selectKeyPair(connection: PoolClient, userIdentity: UserIdentity): Promise<KeyPair> {
|
|
90
|
+
const { rows } = await connection.query('SELECT public_key, private_key FROM public.user WHERE provider = $1 AND user_identifier = $2',
|
|
91
|
+
[userIdentity.provider, userIdentity.id]);
|
|
92
|
+
if (rows.length > 1) {
|
|
93
|
+
throw new Error('Duplicate entries found in the keystore');
|
|
94
|
+
}
|
|
95
|
+
else if (rows.length === 1) {
|
|
96
|
+
const publicPem = <string>rows[0]["public_key"];
|
|
97
|
+
const privatePem = rows[0]["private_key"];
|
|
98
|
+
return { publicPem, privatePem };
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw new Error('No entry found in the keystore');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async getOrGenerateKeyPair(userIdentity: UserIdentity): Promise<KeyPair> {
|
|
106
|
+
const key = getUserIdentityKey(userIdentity);
|
|
107
|
+
if (this.cache.has(key)) {
|
|
108
|
+
return this.cache.get(key);
|
|
109
|
+
}
|
|
110
|
+
const keyPair = await this.connectionFactory.withTransaction(connection =>
|
|
111
|
+
this.selectOrInsertKeyPair(connection, userIdentity));
|
|
112
|
+
this.cache.set(key, keyPair);
|
|
113
|
+
return keyPair;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async selectOrInsertKeyPair(connection: PoolClient, userIdentity: UserIdentity): Promise<KeyPair> {
|
|
117
|
+
const { rows } = await connection.query('SELECT public_key, private_key FROM public.user WHERE provider = $1 AND user_identifier = $2',
|
|
118
|
+
[userIdentity.provider, userIdentity.id]);
|
|
119
|
+
if (rows.length > 1) {
|
|
120
|
+
throw new Error('Duplicate entries found in the keystore');
|
|
121
|
+
}
|
|
122
|
+
else if (rows.length === 1) {
|
|
123
|
+
const publicPem: string = rows[0]["public_key"];
|
|
124
|
+
const privatePem: string = rows[0]["private_key"];
|
|
125
|
+
return { publicPem, privatePem };
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const keyPair = await this.generateKeyPair(connection, userIdentity);
|
|
129
|
+
return keyPair;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async generateKeyPair(connection: PoolClient, userIdentity: UserIdentity): Promise<KeyPair> {
|
|
134
|
+
const keypair = pki.rsa.generateKeyPair({ bits: 2048 });
|
|
135
|
+
const privatePem = pki.privateKeyToPem(keypair.privateKey);
|
|
136
|
+
const publicPem = pki.publicKeyToPem(keypair.publicKey);
|
|
137
|
+
await connection.query('INSERT INTO public.user (provider, user_identifier, private_key, public_key) VALUES ($1, $2, $3, $4)',
|
|
138
|
+
[userIdentity.provider, userIdentity.id, privatePem, publicPem]);
|
|
139
|
+
return { publicPem, privatePem };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getUserIdentityKey(userIdentity: UserIdentity) {
|
|
144
|
+
return `${userIdentity.provider}-${userIdentity.id}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function signFact(fact: FactRecord, publicPem: string, privateKey: pki.rsa.PrivateKey): FactEnvelope {
|
|
148
|
+
const canonicalString = canonicalizeFact(fact.fields, fact.predecessors);
|
|
149
|
+
const encodedString = util.encodeUtf8(canonicalString);
|
|
150
|
+
const digest = md.sha512.create().update(encodedString);
|
|
151
|
+
const hash = util.encode64(digest.digest().getBytes());
|
|
152
|
+
if (fact.hash !== hash) {
|
|
153
|
+
Trace.error(`Hash does not match. "${fact.hash}" !== "${hash}"\nFact: ${canonicalString}`);
|
|
154
|
+
return {
|
|
155
|
+
fact,
|
|
156
|
+
signatures: []
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const signature = util.encode64(privateKey.sign(digest));
|
|
160
|
+
return {
|
|
161
|
+
fact,
|
|
162
|
+
signatures: [{
|
|
163
|
+
signature,
|
|
164
|
+
publicKey: publicPem
|
|
165
|
+
}]
|
|
166
|
+
};
|
|
167
|
+
}
|