mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 17:28:26 +00:00
feat(infisical-pg): test first milestone one flow
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
.eslintrc.js
|
||||||
|
./scripts
|
||||||
@@ -13,12 +13,13 @@ module.exports = {
|
|||||||
tsconfigRootDir: __dirname
|
tsconfigRootDir: __dirname
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"import/prefer-default-export": "off",
|
"consistent-return": "off", // my style
|
||||||
"simple-import-sort/exports": "error",
|
'import/order': 'off', // for simple-import-order
|
||||||
|
"import/prefer-default-export": "off", // why
|
||||||
"import/first": "error",
|
"import/first": "error",
|
||||||
"import/newline-after-import": "error",
|
"import/newline-after-import": "error",
|
||||||
"import/no-duplicates": "error",
|
"import/no-duplicates": "error",
|
||||||
"consistent-return": "off",
|
"simple-import-sort/exports": "error",
|
||||||
"simple-import-sort/imports": [
|
"simple-import-sort/imports": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true
|
"semi": true
|
||||||
}
|
}
|
||||||
Generated
+151
-64
@@ -9,12 +9,14 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@casl/ability": "^6.5.0",
|
||||||
"@fastify/cookie": "^9.2.0",
|
"@fastify/cookie": "^9.2.0",
|
||||||
"@fastify/cors": "^8.4.1",
|
"@fastify/cors": "^8.4.1",
|
||||||
"@fastify/helmet": "^11.1.1",
|
"@fastify/helmet": "^11.1.1",
|
||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/swagger": "^8.12.0",
|
"@fastify/swagger": "^8.12.0",
|
||||||
"@fastify/swagger-ui": "^1.10.1",
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
@@ -27,6 +29,7 @@
|
|||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"picomatch": "^3.0.1",
|
||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
"zod-to-json-schema": "^3.22.0"
|
"zod-to-json-schema": "^3.22.0"
|
||||||
@@ -37,12 +40,13 @@
|
|||||||
"@types/jsrp": "^0.2.6",
|
"@types/jsrp": "^0.2.6",
|
||||||
"@types/node": "^20.9.5",
|
"@types/node": "^20.9.5",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
||||||
"@typescript-eslint/parser": "^6.12.0",
|
"@typescript-eslint/parser": "^6.13.2",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"eslint-plugin-import": "^2.29.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
@@ -63,6 +67,17 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@casl/ability": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/mongo2js": "^1.3.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/stalniy/casl/blob/master/BACKERS.md"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@@ -450,9 +465,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
|
||||||
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
|
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.2",
|
||||||
@@ -513,9 +528,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.54.0",
|
"version": "8.55.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
|
||||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
@@ -1092,6 +1107,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/picomatch": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/prompt-sync": {
|
"node_modules/@types/prompt-sync": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.3.tgz",
|
||||||
@@ -1104,15 +1125,15 @@
|
|||||||
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A=="
|
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A=="
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.2.tgz",
|
||||||
"integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==",
|
"integrity": "sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.5.1",
|
"@eslint-community/regexpp": "^4.5.1",
|
||||||
"@typescript-eslint/scope-manager": "6.12.0",
|
"@typescript-eslint/scope-manager": "6.13.2",
|
||||||
"@typescript-eslint/type-utils": "6.12.0",
|
"@typescript-eslint/type-utils": "6.13.2",
|
||||||
"@typescript-eslint/utils": "6.12.0",
|
"@typescript-eslint/utils": "6.13.2",
|
||||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
"@typescript-eslint/visitor-keys": "6.13.2",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.2.4",
|
||||||
@@ -1159,14 +1180,14 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.2.tgz",
|
||||||
"integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==",
|
"integrity": "sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "6.12.0",
|
"@typescript-eslint/scope-manager": "6.13.2",
|
||||||
"@typescript-eslint/types": "6.12.0",
|
"@typescript-eslint/types": "6.13.2",
|
||||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
"@typescript-eslint/typescript-estree": "6.13.2",
|
||||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
"@typescript-eslint/visitor-keys": "6.13.2",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1207,12 +1228,12 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
|
||||||
"integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==",
|
"integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "6.12.0",
|
"@typescript-eslint/types": "6.13.2",
|
||||||
"@typescript-eslint/visitor-keys": "6.12.0"
|
"@typescript-eslint/visitor-keys": "6.13.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^16.0.0 || >=18.0.0"
|
||||||
@@ -1223,12 +1244,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.2.tgz",
|
||||||
"integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==",
|
"integrity": "sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
"@typescript-eslint/typescript-estree": "6.13.2",
|
||||||
"@typescript-eslint/utils": "6.12.0",
|
"@typescript-eslint/utils": "6.13.2",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.0.1"
|
"ts-api-utils": "^1.0.1"
|
||||||
},
|
},
|
||||||
@@ -1270,9 +1291,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
|
||||||
"integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==",
|
"integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^16.0.0 || >=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -1282,12 +1303,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
|
||||||
"integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==",
|
"integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "6.12.0",
|
"@typescript-eslint/types": "6.13.2",
|
||||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
"@typescript-eslint/visitor-keys": "6.13.2",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -1329,16 +1350,16 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.2.tgz",
|
||||||
"integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==",
|
"integrity": "sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@types/json-schema": "^7.0.12",
|
"@types/json-schema": "^7.0.12",
|
||||||
"@types/semver": "^7.5.0",
|
"@types/semver": "^7.5.0",
|
||||||
"@typescript-eslint/scope-manager": "6.12.0",
|
"@typescript-eslint/scope-manager": "6.13.2",
|
||||||
"@typescript-eslint/types": "6.12.0",
|
"@typescript-eslint/types": "6.13.2",
|
||||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
"@typescript-eslint/typescript-estree": "6.13.2",
|
||||||
"semver": "^7.5.4"
|
"semver": "^7.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1353,11 +1374,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "6.12.0",
|
"version": "6.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
|
||||||
"integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==",
|
"integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "6.12.0",
|
"@typescript-eslint/types": "6.13.2",
|
||||||
"eslint-visitor-keys": "^3.4.1"
|
"eslint-visitor-keys": "^3.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1368,6 +1389,37 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ucast/core": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/js": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/mongo": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/mongo2js": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.6.1",
|
||||||
|
"@ucast/js": "^3.0.0",
|
||||||
|
"@ucast/mongo": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ungap/structured-clone": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||||
@@ -1523,6 +1575,18 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/anymatch/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/aproba": {
|
"node_modules/aproba": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||||
@@ -2453,14 +2517,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.54.0",
|
"version": "8.55.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
|
||||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
"@eslint/eslintrc": "^2.1.3",
|
"@eslint/eslintrc": "^2.1.4",
|
||||||
"@eslint/js": "8.54.0",
|
"@eslint/js": "8.55.0",
|
||||||
"@humanwhocodes/config-array": "^0.11.13",
|
"@humanwhocodes/config-array": "^0.11.13",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
@@ -2547,9 +2611,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-prettier": {
|
"node_modules/eslint-config-prettier": {
|
||||||
"version": "9.0.0",
|
"version": "9.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
|
||||||
"integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==",
|
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
@@ -4576,6 +4640,17 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/micromatch/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mime": {
|
"node_modules/mime": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||||
@@ -5192,11 +5267,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
@@ -5471,6 +5546,18 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/readdirp/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/real-require": {
|
"node_modules/real-require": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"dev": "tsx watch --clear-screen=false ./src/server/app.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
"dev": "tsx watch --clear-screen=false ./src/server/app.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
||||||
"dev:docker": "nodemon",
|
"dev:docker": "nodemon",
|
||||||
"type:check": "tsc --noEmit",
|
"type:check": "tsc --noEmit",
|
||||||
"lint:fix": "eslint --fix 'src/**/*.ts'",
|
"lint:fix": "eslint --fix --ext js,ts ./src",
|
||||||
"lint": "eslint 'src/**/*.ts'",
|
"lint": "eslint 'src/**/*.ts'",
|
||||||
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
||||||
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
||||||
@@ -28,12 +28,13 @@
|
|||||||
"@types/jsrp": "^0.2.6",
|
"@types/jsrp": "^0.2.6",
|
||||||
"@types/node": "^20.9.5",
|
"@types/node": "^20.9.5",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
||||||
"@typescript-eslint/parser": "^6.12.0",
|
"@typescript-eslint/parser": "^6.13.2",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"eslint-plugin-import": "^2.29.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
@@ -46,12 +47,14 @@
|
|||||||
"typescript": "^5.3.2"
|
"typescript": "^5.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@casl/ability": "^6.5.0",
|
||||||
"@fastify/cookie": "^9.2.0",
|
"@fastify/cookie": "^9.2.0",
|
||||||
"@fastify/cors": "^8.4.1",
|
"@fastify/cors": "^8.4.1",
|
||||||
"@fastify/helmet": "^11.1.1",
|
"@fastify/helmet": "^11.1.1",
|
||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/swagger": "^8.12.0",
|
"@fastify/swagger": "^8.12.0",
|
||||||
"@fastify/swagger-ui": "^1.10.1",
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
@@ -64,6 +67,7 @@
|
|||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"picomatch": "^3.0.1",
|
||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
"zod-to-json-schema": "^3.22.0"
|
"zod-to-json-schema": "^3.22.0"
|
||||||
|
|||||||
@@ -16,11 +16,18 @@ const componentType = parseInt(prompt("Select a component: "), 10);
|
|||||||
if (componentType === 1) {
|
if (componentType === 1) {
|
||||||
const componentName = prompt("Enter service name: ");
|
const componentName = prompt("Enter service name: ");
|
||||||
const dir = path.join(__dirname, `../src/services/${componentName}`);
|
const dir = path.join(__dirname, `../src/services/${componentName}`);
|
||||||
const capitalizedComponentName = componentName.at(0)?.toUpperCase() + componentName.slice(1);
|
const pascalCase = componentName
|
||||||
const dalTypeName = `T${capitalizedComponentName}DalFactory`;
|
.split("-")
|
||||||
const dalName = `${componentName}DalFactory`;
|
.map((el) => `${el[0].toUpperCase()}${el.slice(1)}`)
|
||||||
const serviceTypeName = `T${capitalizedComponentName}ServiceFactory`;
|
.join("");
|
||||||
const serviceName = `${componentName}ServiceFactory`;
|
const camelCase = componentName
|
||||||
|
.split("-")
|
||||||
|
.map((el, index) => (index === 0 ? el : `${el[0].toUpperCase()}${el.slice(1)}`))
|
||||||
|
.join("");
|
||||||
|
const dalTypeName = `T${pascalCase}DalFactory`;
|
||||||
|
const dalName = `${camelCase}DalFactory`;
|
||||||
|
const serviceTypeName = `T${pascalCase}ServiceFactory`;
|
||||||
|
const serviceName = `${camelCase}ServiceFactory`;
|
||||||
|
|
||||||
mkdirSync(dir);
|
mkdirSync(dir);
|
||||||
|
|
||||||
@@ -29,9 +36,9 @@ if (componentType === 1) {
|
|||||||
`import { TDbClient } from "@app/db";
|
`import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
export type ${dalTypeName} = {};
|
export type ${dalTypeName} = ReturnType<typeof ${dalName}>;
|
||||||
|
|
||||||
export const ${dalName} = (db: TDbClient): ${dalTypeName} => {
|
export const ${dalName} = (db: TDbClient) => {
|
||||||
|
|
||||||
return { };
|
return { };
|
||||||
};
|
};
|
||||||
@@ -43,12 +50,12 @@ export const ${dalName} = (db: TDbClient): ${dalTypeName} => {
|
|||||||
`import { ${dalTypeName} } from "./${componentName}-dal";
|
`import { ${dalTypeName} } from "./${componentName}-dal";
|
||||||
|
|
||||||
type ${serviceTypeName}Dep = {
|
type ${serviceTypeName}Dep = {
|
||||||
${componentName}Dal: ${dalTypeName};
|
${camelCase}Dal: ${dalTypeName};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ${serviceTypeName} = ReturnType<typeof ${serviceName}>;
|
export type ${serviceTypeName} = ReturnType<typeof ${serviceName}>;
|
||||||
|
|
||||||
export const ${serviceName} = ({ ${componentName}Dal }: ${serviceTypeName}Dep) => {
|
export const ${serviceName} = ({ ${camelCase}Dal }: ${serviceTypeName}Dep) => {
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from "path";
|
|||||||
import knex from "knex";
|
import knex from "knex";
|
||||||
import { appendFileSync, readFileSync, writeFileSync } from "fs";
|
import { appendFileSync, readFileSync, writeFileSync } from "fs";
|
||||||
import promptSync from "prompt-sync";
|
import promptSync from "prompt-sync";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
const prompt = promptSync();
|
const prompt = promptSync();
|
||||||
|
|
||||||
@@ -27,11 +28,11 @@ const getZodPrimitiveType = (type: string) => {
|
|||||||
case "boolean":
|
case "boolean":
|
||||||
return "z.boolean()";
|
return "z.boolean()";
|
||||||
case "jsonb":
|
case "jsonb":
|
||||||
return "z.string()";
|
return "z.unknown()";
|
||||||
case "json":
|
case "json":
|
||||||
return "z.string()";
|
return "z.unknown()";
|
||||||
case "timestamp with time zone":
|
case "timestamp with time zone":
|
||||||
return "z.string().datetime()";
|
return "z.date()";
|
||||||
case "integer":
|
case "integer":
|
||||||
return "z.number()";
|
return "z.number()";
|
||||||
case "text":
|
case "text":
|
||||||
|
|||||||
Vendored
+18
-5
@@ -1,10 +1,16 @@
|
|||||||
import { TUser } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAuthDalFactory } from "@app/services/auth/auth-dal";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||||
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
||||||
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
||||||
import { AuthMode } from "@app/services/auth/auth-signup-type";
|
import { AuthMode } from "@app/services/auth/auth-signup-type";
|
||||||
|
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
||||||
|
import { TOrgServiceFactory } from "@app/services/org/org-service";
|
||||||
|
import { TServerCfgServiceFactory } from "@app/services/server-cfg/server-cfg-service";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/token/token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/token/token-service";
|
||||||
|
import { TUserDalFactory } from "@app/services/user/user-dal";
|
||||||
|
import { TUserServiceFactory } from "@app/services/user/user-service";
|
||||||
|
|
||||||
import "fastify";
|
import "fastify";
|
||||||
|
|
||||||
@@ -14,13 +20,14 @@ declare module "fastify" {
|
|||||||
// used for mfa session authentication
|
// used for mfa session authentication
|
||||||
mfa: {
|
mfa: {
|
||||||
userId: string;
|
userId: string;
|
||||||
user: TUser;
|
user: TUsers;
|
||||||
};
|
};
|
||||||
// identity injection. depending on which kinda of token the information is filled in auth
|
// identity injection. depending on which kinda of token the information is filled in auth
|
||||||
auth: {
|
auth: {
|
||||||
authMode: AuthMode.JWT | AuthMode.API_KEY_V2 | AuthMode.API_KEY;
|
authMode: AuthMode.JWT | AuthMode.API_KEY_V2 | AuthMode.API_KEY;
|
||||||
userId: string;
|
userId: string;
|
||||||
user: TUser;
|
tokenVersionId: string; // the session id of token used
|
||||||
|
user: TUsers;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,12 +37,18 @@ declare module "fastify" {
|
|||||||
password: TAuthPasswordFactory;
|
password: TAuthPasswordFactory;
|
||||||
signup: TAuthSignupFactory;
|
signup: TAuthSignupFactory;
|
||||||
authToken: TAuthTokenServiceFactory;
|
authToken: TAuthTokenServiceFactory;
|
||||||
|
permission: TPermissionServiceFactory;
|
||||||
|
org: TOrgServiceFactory;
|
||||||
|
orgRole: TOrgRoleServiceFactory;
|
||||||
|
serverCfg: TServerCfgServiceFactory;
|
||||||
|
user: TUserServiceFactory;
|
||||||
|
apiKey: TApiKeyServiceFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
store: {
|
store: {
|
||||||
user: Pick<TAuthDalFactory, "getUserById">;
|
user: Pick<TUserDalFactory, "findById">;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+36
-4
@@ -10,10 +10,24 @@ import {
|
|||||||
TBackupPrivateKey,
|
TBackupPrivateKey,
|
||||||
TBackupPrivateKeyInsert,
|
TBackupPrivateKeyInsert,
|
||||||
TBackupPrivateKeyUpdate,
|
TBackupPrivateKeyUpdate,
|
||||||
TOrganizationMemberships,
|
TIncidentContacts,
|
||||||
|
TIncidentContactsInsert,
|
||||||
|
TIncidentContactsUpdate,
|
||||||
TOrganizations,
|
TOrganizations,
|
||||||
TOrganizationsInsert,
|
TOrganizationsInsert,
|
||||||
TOrganizationsUpdate,
|
TOrganizationsUpdate,
|
||||||
|
TOrgMemberships,
|
||||||
|
TOrgMembershipsInsert,
|
||||||
|
TOrgMembershipsUpdate,
|
||||||
|
TOrgRoles,
|
||||||
|
TOrgRolesInsert,
|
||||||
|
TOrgRolesUpdate,
|
||||||
|
TServerConfig,
|
||||||
|
TServerConfigInsert,
|
||||||
|
TServerConfigUpdate,
|
||||||
|
TUserActions,
|
||||||
|
TUserActionsInsert,
|
||||||
|
TUserActionsUpdate,
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
TUserEncryptionKeysUpdate,
|
TUserEncryptionKeysUpdate,
|
||||||
@@ -21,6 +35,7 @@ import {
|
|||||||
TUsersInsert,
|
TUsersInsert,
|
||||||
TUsersUpdate
|
TUsersUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { TApiKeys, TApiKeysInsert, TApiKeysUpdate } from "@app/db/schemas/api-keys";
|
||||||
|
|
||||||
declare module "knex/types/tables" {
|
declare module "knex/types/tables" {
|
||||||
interface Tables extends { [key in TableName]: Knex.CompositeTableType<any> } {
|
interface Tables extends { [key in TableName]: Knex.CompositeTableType<any> } {
|
||||||
@@ -51,9 +66,26 @@ declare module "knex/types/tables" {
|
|||||||
TOrganizationsUpdate
|
TOrganizationsUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.OrgMembership]: Knex.CompositeTableType<
|
[TableName.OrgMembership]: Knex.CompositeTableType<
|
||||||
TOrganizationMemberships,
|
TOrgMemberships,
|
||||||
TOrganizationsInsert,
|
TOrgMembershipsInsert,
|
||||||
TOrganizationsUpdate
|
TOrgMembershipsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.OrgRoles]: Knex.CompositeTableType<TOrgRoles, TOrgRolesInsert, TOrgRolesUpdate>;
|
||||||
|
[TableName.IncidentContact]: Knex.CompositeTableType<
|
||||||
|
TIncidentContacts,
|
||||||
|
TIncidentContactsInsert,
|
||||||
|
TIncidentContactsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.UserAction]: Knex.CompositeTableType<
|
||||||
|
TUserActions,
|
||||||
|
TUserActionsInsert,
|
||||||
|
TUserActionsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ServerConfig]: Knex.CompositeTableType<
|
||||||
|
TServerConfig,
|
||||||
|
TServerConfigInsert,
|
||||||
|
TServerConfigUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ApiKey]: Knex.CompositeTableType<TApiKeys, TApiKeysInsert, TApiKeysUpdate>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.text("clientPublicKey");
|
t.text("clientPublicKey");
|
||||||
t.text("serverPrivateKey");
|
t.text("serverPrivateKey");
|
||||||
t.integer("encryptionVersion").defaultTo(1);
|
t.integer("encryptionVersion").defaultTo(2);
|
||||||
t.text("protectedKey").notNullable();
|
t.text("protectedKey").notNullable();
|
||||||
t.text("protectedKeyIV").notNullable();
|
t.text("protectedKeyIV").notNullable();
|
||||||
t.text("protectedKeyTag").notNullable();
|
t.text("protectedKeyTag").notNullable();
|
||||||
|
|||||||
@@ -13,12 +13,21 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
// does not need update trigger we will do it manually
|
// does not need update trigger we will do it manually
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
|
await knex.schema.alterTable(TableName.AuthTokens, (t) => {
|
||||||
|
t.uuid("orgId");
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// this is a one time function
|
// this is a one time function
|
||||||
await createOnUpdateTrigger(knex, TableName.Organization);
|
await createOnUpdateTrigger(knex, TableName.Organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.AuthTokens, "orgId")) {
|
||||||
|
await knex.schema.alterTable(TableName.AuthTokens, (t) => {
|
||||||
|
t.dropColumn("orgId");
|
||||||
|
});
|
||||||
|
}
|
||||||
await knex.schema.dropTableIfExists(TableName.Organization);
|
await knex.schema.dropTableIfExists(TableName.Organization);
|
||||||
await dropOnUpdateTrigger(knex, TableName.Organization);
|
await dropOnUpdateTrigger(knex, TableName.Organization);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,23 @@ import { OrgMembershipStatus } from "../schemas/models";
|
|||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
const isTablePresent = await knex.schema.hasTable(TableName.OrgMembership);
|
const isOrgRolePresent = await knex.schema.hasTable(TableName.OrgRoles);
|
||||||
if (!isTablePresent) {
|
if (!isOrgRolePresent) {
|
||||||
|
await knex.schema.createTable(TableName.OrgRoles, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.string("description");
|
||||||
|
t.string("slug").notNullable();
|
||||||
|
t.json("permissions").notNullable();
|
||||||
|
// does not need update trigger we will do it manually
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOrgTablePresent = await knex.schema.hasTable(TableName.OrgMembership);
|
||||||
|
if (!isOrgTablePresent) {
|
||||||
await knex.schema.createTable(TableName.OrgMembership, (t) => {
|
await knex.schema.createTable(TableName.OrgMembership, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.string("role").notNullable();
|
t.string("role").notNullable();
|
||||||
@@ -14,10 +29,13 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.string("inviteEmail");
|
t.string("inviteEmail");
|
||||||
// does not need update trigger we will do it manually
|
// does not need update trigger we will do it manually
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
t.uuid("userId").notNullable();
|
t.uuid("userId");
|
||||||
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
t.uuid("orgId").notNullable();
|
t.uuid("orgId").notNullable();
|
||||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
// until role is changed/removed the role should not deleted
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// this is a one time function
|
// this is a one time function
|
||||||
@@ -26,5 +44,6 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
await knex.schema.dropTableIfExists(TableName.OrgMembership);
|
await knex.schema.dropTableIfExists(TableName.OrgMembership);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.OrgRoles);
|
||||||
await dropOnUpdateTrigger(knex, TableName.OrgMembership);
|
await dropOnUpdateTrigger(knex, TableName.OrgMembership);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const isTablePresent = await knex.schema.hasTable(TableName.IncidentContact);
|
||||||
|
if (!isTablePresent) {
|
||||||
|
await knex.schema.createTable(TableName.IncidentContact, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("email").notNullable();
|
||||||
|
// does not need update trigger we will do it manually
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// this is a one time function
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IncidentContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IncidentContact);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IncidentContact);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const isTablePresent = await knex.schema.hasTable(TableName.UserAction);
|
||||||
|
if (!isTablePresent) {
|
||||||
|
await knex.schema.createTable(TableName.UserAction, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("action").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("userId").notNullable();
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.UserAction);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const isTablePresent = await knex.schema.hasTable(TableName.ServerConfig);
|
||||||
|
if (!isTablePresent) {
|
||||||
|
await knex.schema.createTable(TableName.ServerConfig, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.boolean("initialized").defaultTo(false);
|
||||||
|
t.boolean("allowSignUp").defaultTo(true);
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// this is a one time function
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ServerConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ServerConfig);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ServerConfig);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const isTablePresent = await knex.schema.hasTable(TableName.ApiKey);
|
||||||
|
if (!isTablePresent) {
|
||||||
|
await knex.schema.createTable(TableName.ApiKey, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.datetime("lastUsed");
|
||||||
|
t.datetime("expiresAt");
|
||||||
|
t.string("secretHash").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("userId").notNullable();
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ApiKey);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ApiKey);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ApiKeysSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
expiresAt: z.date().nullable().optional(),
|
||||||
|
secretHash: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TApiKeys = z.infer<typeof ApiKeysSchema>;
|
||||||
|
export type TApiKeysInsert = Omit<TApiKeys, TImmutableDBKeys>;
|
||||||
|
export type TApiKeysUpdate = Partial<Omit<TApiKeys, TImmutableDBKeys>>;
|
||||||
@@ -13,9 +13,9 @@ export const AuthTokenSessionsSchema = z.object({
|
|||||||
userAgent: z.string().nullable().optional(),
|
userAgent: z.string().nullable().optional(),
|
||||||
refreshVersion: z.number().default(1),
|
refreshVersion: z.number().default(1),
|
||||||
accessVersion: z.number().default(1),
|
accessVersion: z.number().default(1),
|
||||||
lastUsed: z.string().datetime(),
|
lastUsed: z.date(),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.date(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string().uuid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ export const AuthTokensSchema = z.object({
|
|||||||
phoneNumber: z.string().nullable().optional(),
|
phoneNumber: z.string().nullable().optional(),
|
||||||
tokenHash: z.string(),
|
tokenHash: z.string(),
|
||||||
triesLeft: z.number().nullable().optional(),
|
triesLeft: z.number().nullable().optional(),
|
||||||
expiresAt: z.string().datetime(),
|
expiresAt: z.date(),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.date(),
|
||||||
userId: z.string().uuid().nullable().optional(),
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAuthTokens = z.infer<typeof AuthTokensSchema>;
|
export type TAuthTokens = z.infer<typeof AuthTokensSchema>;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export const BackupPrivateKeySchema = z.object({
|
|||||||
keyEncoding: z.string(),
|
keyEncoding: z.string(),
|
||||||
salt: z.string(),
|
salt: z.string(),
|
||||||
verifier: z.string(),
|
verifier: z.string(),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.date(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string().uuid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IncidentContactsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
email: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIncidentContacts = z.infer<typeof IncidentContactsSchema>;
|
||||||
|
export type TIncidentContactsInsert = Omit<TIncidentContacts, TImmutableDBKeys>;
|
||||||
|
export type TIncidentContactsUpdate = Partial<Omit<TIncidentContacts, TImmutableDBKeys>>;
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
export * from "./auth-token-sessions";
|
export * from "./auth-token-sessions";
|
||||||
export * from "./auth-tokens";
|
export * from "./auth-tokens";
|
||||||
export * from "./backup-private-key";
|
export * from "./backup-private-key";
|
||||||
|
export * from "./incident-contacts";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
export * from "./organization-memberships";
|
export * from "./org-memberships";
|
||||||
|
export * from "./org-roles";
|
||||||
export * from "./organizations";
|
export * from "./organizations";
|
||||||
|
export * from "./server-config";
|
||||||
|
export * from "./user-actions";
|
||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ export enum TableName {
|
|||||||
AuthTokenSession = "auth_token_sessions",
|
AuthTokenSession = "auth_token_sessions",
|
||||||
BackupPrivateKey = "backup_private_key",
|
BackupPrivateKey = "backup_private_key",
|
||||||
Organization = "organizations",
|
Organization = "organizations",
|
||||||
OrgMembership = "organization_memberships"
|
OrgMembership = "org_memberships",
|
||||||
|
OrgRoles = "org_roles",
|
||||||
|
IncidentContact = "incident_contacts",
|
||||||
|
UserAction = "user_actions",
|
||||||
|
ServerConfig = "server_config",
|
||||||
|
ApiKey = "api_keys"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const OrgMembershipsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
role: z.string(),
|
||||||
|
status: z.string().default('invited'),
|
||||||
|
inviteEmail: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
roleId: z.string().uuid().nullable().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
|
||||||
|
export type TOrgMembershipsInsert = Omit<TOrgMemberships, TImmutableDBKeys>;
|
||||||
|
export type TOrgMembershipsUpdate = Partial<Omit<TOrgMemberships, TImmutableDBKeys>>;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const OrgRolesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
slug: z.string(),
|
||||||
|
permissions: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TOrgRoles = z.infer<typeof OrgRolesSchema>;
|
||||||
|
export type TOrgRolesInsert = Omit<TOrgRoles, TImmutableDBKeys>;
|
||||||
|
export type TOrgRolesUpdate = Partial<Omit<TOrgRoles, TImmutableDBKeys>>;
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Code generated by automation script, DO NOT EDIT.
|
|
||||||
// Automated by pulling database and generating zod schema
|
|
||||||
// To update. Just run npm run generate:schema
|
|
||||||
// Written by akhilmhdh.
|
|
||||||
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
|
||||||
|
|
||||||
export const OrganizationMembershipsSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
role: z.string(),
|
|
||||||
status: z.string().default('invited'),
|
|
||||||
inviteEmail: z.string().nullable().optional(),
|
|
||||||
createdAt: z.string().datetime(),
|
|
||||||
updatedAt: z.string().datetime(),
|
|
||||||
userId: z.string().uuid(),
|
|
||||||
orgId: z.string().uuid(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TOrganizationMemberships = z.infer<typeof OrganizationMembershipsSchema>;
|
|
||||||
export type TOrganizationMembershipsInsert = Omit<TOrganizationMemberships, TImmutableDBKeys>;
|
|
||||||
export type TOrganizationMembershipsUpdate = Partial<Omit<TOrganizationMemberships, TImmutableDBKeys>>;
|
|
||||||
@@ -11,8 +11,8 @@ export const OrganizationsSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
customerId: z.string().nullable().optional(),
|
customerId: z.string().nullable().optional(),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ServerConfigSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
initialized: z.boolean().default(false).nullable().optional(),
|
||||||
|
allowSignUp: z.boolean().default(true).nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TServerConfig = z.infer<typeof ServerConfigSchema>;
|
||||||
|
export type TServerConfigInsert = Omit<TServerConfig, TImmutableDBKeys>;
|
||||||
|
export type TServerConfigUpdate = Partial<Omit<TServerConfig, TImmutableDBKeys>>;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const UserActionsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
action: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUserActions = z.infer<typeof UserActionsSchema>;
|
||||||
|
export type TUserActionsInsert = Omit<TUserActions, TImmutableDBKeys>;
|
||||||
|
export type TUserActionsUpdate = Partial<Omit<TUserActions, TImmutableDBKeys>>;
|
||||||
@@ -17,9 +17,9 @@ export const UsersSchema = z.object({
|
|||||||
isAccepted: z.boolean().default(false).nullable().optional(),
|
isAccepted: z.boolean().default(false).nullable().optional(),
|
||||||
isMfaEnabled: z.boolean().default(false).nullable().optional(),
|
isMfaEnabled: z.boolean().default(false).nullable().optional(),
|
||||||
mfaMethods: z.string().array().nullable().optional(),
|
mfaMethods: z.string().array().nullable().optional(),
|
||||||
devices: z.string().nullable().optional(),
|
devices: z.unknown().nullable().optional(),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TUsers = z.infer<typeof UsersSchema>;
|
export type TUsers = z.infer<typeof UsersSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
The Infisical Enterprise license (the “Enterprise License”)
|
||||||
|
Copyright (c) 2022 Infisical Inc
|
||||||
|
|
||||||
|
With regard to the Infisical Software:
|
||||||
|
|
||||||
|
This software and associated documentation files (the "Software") may only be
|
||||||
|
used in production, if you (and any entity that you represent) have agreed to,
|
||||||
|
and are in compliance with, the Infisical Subscription Terms of Service, available
|
||||||
|
at https://infisical.com/terms (the “Enterprise Terms”), or other
|
||||||
|
agreement governing the use of the Software, as agreed by you and Infisical,
|
||||||
|
and otherwise have a valid Infisical Enterprise License for the
|
||||||
|
correct number of user seats. Subject to the foregoing sentence, you are free to
|
||||||
|
modify this Software and publish patches to the Software. You agree that Infisical
|
||||||
|
and/or its licensors (as applicable) retain all right, title and interest in and
|
||||||
|
to all such modifications and/or patches, and all such modifications and/or
|
||||||
|
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||||
|
exploited with a valid Infiscial Enterprise subscription for the correct
|
||||||
|
number of user seats. Notwithstanding the foregoing, you may copy and modify
|
||||||
|
the Software for development and testing purposes, without requiring a
|
||||||
|
subscription. You agree that Infisical and/or its licensors (as applicable) retain
|
||||||
|
all right, title and interest in and to all such modifications. You are not
|
||||||
|
granted any other rights beyond what is expressly stated herein. Subject to the
|
||||||
|
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
For all third party components incorporated into the Infisical Software, those
|
||||||
|
components are licensed under the original license provided by the owner of the
|
||||||
|
applicable component.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { registerOrgRoleRouter } from "./org-role";
|
||||||
|
|
||||||
|
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||||
|
// org role starts with organization
|
||||||
|
await server.register(registerOrgRoleRouter, { prefix: "/organization" });
|
||||||
|
};
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:organizationId/roles",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z.string().trim(),
|
||||||
|
name: z.string().trim(),
|
||||||
|
description: z.string().trim().optional(),
|
||||||
|
workspaceId: z.string().trim().optional(),
|
||||||
|
orgId: z.string().trim(),
|
||||||
|
permissions: z.any().array()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: OrgRolesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.orgRole.createRole(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId,
|
||||||
|
{ ...req.body, permissions: JSON.stringify(req.body.permissions) }
|
||||||
|
);
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:organizationId/roles/:roleId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
roleId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z.string().trim().optional(),
|
||||||
|
name: z.string().trim().optional(),
|
||||||
|
description: z.string().trim().optional(),
|
||||||
|
workspaceId: z.string().trim().optional(),
|
||||||
|
orgId: z.string().trim(),
|
||||||
|
permissions: z.any().array()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: OrgRolesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.orgRole.updateRole(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId,
|
||||||
|
req.params.roleId,
|
||||||
|
{
|
||||||
|
...req.body,
|
||||||
|
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/roles/:roleId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
roleId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: OrgRolesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.orgRole.deleteRole(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId,
|
||||||
|
req.params.roleId
|
||||||
|
);
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/roles",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
roles: OrgRolesSchema.omit({ permissions: true })
|
||||||
|
.merge(z.object({ permissions: z.unknown() }))
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const roles = await server.services.orgRole.listRoles(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { roles };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/permissions",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
membership: OrgMembershipsSchema,
|
||||||
|
permissions: z.any().array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { permissions, membership } = await server.services.orgRole.getUserPermission(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { permissions, membership };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AbilityBuilder,
|
||||||
|
buildMongoQueryMatcher,
|
||||||
|
createMongoAbility,
|
||||||
|
MongoAbility
|
||||||
|
} from "@casl/ability";
|
||||||
|
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||||
|
|
||||||
|
const $glob: FieldInstruction<string> = {
|
||||||
|
type: "field",
|
||||||
|
validate(instruction, value) {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
throw new Error(`"${instruction.name}" expects value to be a string`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const glob: JsInterpreter<FieldCondition<string>> = (node, object, context) => {
|
||||||
|
const secretPath = context.get(object, node.field);
|
||||||
|
const permissionSecretGlobPath = node.value;
|
||||||
|
return picomatch.isMatch(secretPath, permissionSecretGlobPath, { strictSlashes: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
||||||
|
|
||||||
|
export enum OrgPermissionActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionSubjects {
|
||||||
|
Workspace = "workspace",
|
||||||
|
Role = "role",
|
||||||
|
Member = "member",
|
||||||
|
Settings = "settings",
|
||||||
|
IncidentAccount = "incident-contact",
|
||||||
|
Sso = "sso",
|
||||||
|
Billing = "billing",
|
||||||
|
SecretScanning = "secret-scanning"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrgPermissionSet =
|
||||||
|
| [OrgPermissionActions.Read, OrgPermissionSubjects.Workspace]
|
||||||
|
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Role]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Member]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Settings]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing];
|
||||||
|
|
||||||
|
const buildAdminPermission = () => {
|
||||||
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
|
// ws permissions
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
|
// role permission
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Role);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Sso);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
|
return build({ conditionsMatcher });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const orgAdminPermissions = buildAdminPermission();
|
||||||
|
|
||||||
|
const buildMemberPermission = () => {
|
||||||
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
|
return build({ conditionsMatcher });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const orgMemberPermissions = buildMemberPermission();
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName, TOrgMemberships } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export type TPermissionDalFactory = ReturnType<typeof permissionDalFactory>;
|
||||||
|
|
||||||
|
export const permissionDalFactory = (db: TDbClient) => {
|
||||||
|
const getOrgPermission = async (
|
||||||
|
userId: string,
|
||||||
|
orgId: string
|
||||||
|
): Promise<(TOrgMemberships & { permissions: string }) | undefined> => {
|
||||||
|
const membership = await db(TableName.OrgMembership)
|
||||||
|
.leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
|
.select(`${TableName.OrgMembership}.*`, `${TableName.OrgRoles}.permissions`)
|
||||||
|
.where({ userId, [`${TableName.OrgMembership}.orgId`]: orgId })
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getOrgPermission
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import {
|
||||||
|
conditionsMatcher,
|
||||||
|
orgAdminPermissions,
|
||||||
|
orgMemberPermissions,
|
||||||
|
OrgPermissionSet
|
||||||
|
} from "./org-permission";
|
||||||
|
import { TPermissionDalFactory } from "./permission-dal";
|
||||||
|
|
||||||
|
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
|
import { unpackRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
|
type TPermissionServiceFactoryDep = {
|
||||||
|
permissionDal: TPermissionDalFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TPermissionServiceFactory = ReturnType<typeof permissionServiceFactory>;
|
||||||
|
|
||||||
|
export const permissionServiceFactory = ({ permissionDal }: TPermissionServiceFactoryDep) => {
|
||||||
|
/*
|
||||||
|
* Get user permission in an organization
|
||||||
|
* */
|
||||||
|
const getUserOrgPermission = async (userId: string, orgId: string) => {
|
||||||
|
const membership = await permissionDal.getOrgPermission(userId, orgId);
|
||||||
|
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
|
||||||
|
if (membership.role === "custom" && !membership.permissions) {
|
||||||
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (membership.role === "admin") return { permission: orgAdminPermissions, membership };
|
||||||
|
if (membership.role === "member") return { permission: orgMemberPermissions, membership };
|
||||||
|
if (membership.role === "custom") {
|
||||||
|
const permission = createMongoAbility<OrgPermissionSet>(
|
||||||
|
// akhilmhdh: putting any due to ts incompatiable matching with string and the other
|
||||||
|
unpackRules<RawRuleOf<MongoAbility<OrgPermissionSet>>>(membership.permissions as any),
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { permission, membership };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ name: "Role missing", message: "User role not found" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getUserOrgPermission
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,33 +8,35 @@ const zodStrBool = z
|
|||||||
.optional()
|
.optional()
|
||||||
.transform((val) => val === "true");
|
.transform((val) => val === "true");
|
||||||
|
|
||||||
const envSchema = z.object({
|
const envSchema = z
|
||||||
PORT: z.coerce.number().default(4000),
|
.object({
|
||||||
HOST: zpStr(z.string().default("localhost")),
|
PORT: z.coerce.number().default(4000),
|
||||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")),
|
HOST: zpStr(z.string().default("localhost")),
|
||||||
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")),
|
||||||
SALT_ROUNDS: z.coerce.number().default(10),
|
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
||||||
// TODO(akhilmhdh): will be changed to one
|
SALT_ROUNDS: z.coerce.number().default(10),
|
||||||
ENCRYPTION_KEY: zpStr(z.string().optional()),
|
// TODO(akhilmhdh): will be changed to one
|
||||||
ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()),
|
ENCRYPTION_KEY: zpStr(z.string().optional()),
|
||||||
HTTPS_ENABLED: zodStrBool,
|
ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()),
|
||||||
// smtp options
|
HTTPS_ENABLED: zodStrBool,
|
||||||
SMTP_HOST: zpStr(z.string().optional()),
|
// smtp options
|
||||||
SMTP_SECURE: zodStrBool,
|
SMTP_HOST: zpStr(z.string().optional()),
|
||||||
SMTP_PORT: z.coerce.number().default(587),
|
SMTP_SECURE: zodStrBool,
|
||||||
SMTP_USERNAME: zpStr(z.string().optional()),
|
SMTP_PORT: z.coerce.number().default(587),
|
||||||
SMTP_PASSWORD: zpStr(z.string().optional()),
|
SMTP_USERNAME: zpStr(z.string().optional()),
|
||||||
SMTP_FROM_ADDRESS: zpStr(z.string().optional()),
|
SMTP_PASSWORD: zpStr(z.string().optional()),
|
||||||
SMTP_FROM_NAME: zpStr(z.string().optional().default("Infisical")),
|
SMTP_FROM_ADDRESS: zpStr(z.string().optional()),
|
||||||
COOKIE_SECRET_SIGN_KEY: z.string().default("g5giLbOMpaJhqEogXApkiw2ZFW5Q0jvA"),
|
SMTP_FROM_NAME: zpStr(z.string().optional().default("Infisical")),
|
||||||
SITE_URL: zpStr(z.string().optional()),
|
COOKIE_SECRET_SIGN_KEY: z.string().default("g5giLbOMpaJhqEogXApkiw2ZFW5Q0jvA"),
|
||||||
// jwt options
|
SITE_URL: zpStr(z.string().optional()),
|
||||||
JWT_AUTH_SECRET: zpStr(z.string()),
|
// jwt options
|
||||||
JWT_AUTH_LIFETIME: zpStr(z.string().default("10d")),
|
JWT_AUTH_SECRET: zpStr(z.string()),
|
||||||
JWT_SIGNUP_LIFETIME: zpStr(z.string().default("15m")),
|
JWT_AUTH_LIFETIME: zpStr(z.string().default("10d")),
|
||||||
JWT_REFRESH_LIFETIME: zpStr(z.string().default("90d")),
|
JWT_SIGNUP_LIFETIME: zpStr(z.string().default("15m")),
|
||||||
JWT_MFA_LIFETIME: zpStr(z.string().default("5m"))
|
JWT_REFRESH_LIFETIME: zpStr(z.string().default("90d")),
|
||||||
});
|
JWT_MFA_LIFETIME: zpStr(z.string().default("5m"))
|
||||||
|
})
|
||||||
|
.transform((data) => ({ ...data, isSmtpConfigured: Boolean(data.SMTP_HOST) }));
|
||||||
|
|
||||||
let envCfg: Readonly<z.infer<typeof envSchema>>;
|
let envCfg: Readonly<z.infer<typeof envSchema>>;
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class BadRequestError extends Error {
|
|||||||
|
|
||||||
error: unknown;
|
error: unknown;
|
||||||
|
|
||||||
constructor({ name, error, message }: { message?: string; name: string; error: unknown }) {
|
constructor({ name, error, message }: { message?: string; name: string; error?: unknown }) {
|
||||||
super(message ?? "The request is invalid");
|
super(message ?? "The request is invalid");
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.error = error;
|
this.error = error;
|
||||||
|
|||||||
@@ -1,10 +1,108 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
import { DatabaseError } from "../errors";
|
||||||
|
|
||||||
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
||||||
transaction: async <T>(cb: (tx: Knex) => T) =>
|
transaction: async <T>(cb: (tx: Knex) => Promise<T>) =>
|
||||||
db.transaction(async (trx) => {
|
db.transaction(async (trx) => {
|
||||||
const res = await cb(trx);
|
const res = await cb(trx);
|
||||||
return res;
|
return res;
|
||||||
}),
|
}),
|
||||||
...dal
|
...dal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// What is ormify
|
||||||
|
// It is to inject typical operations like find, findOne, update, delete, create
|
||||||
|
// This will avoid writing most common ones each time
|
||||||
|
export const ormify = <DbOps extends object, Tname extends TableName>(
|
||||||
|
db: Knex,
|
||||||
|
tableName: Tname,
|
||||||
|
dal?: DbOps
|
||||||
|
) => ({
|
||||||
|
transaction: async <T>(cb: (tx: Knex) => Promise<T>) =>
|
||||||
|
db.transaction(async (trx) => {
|
||||||
|
const res = await cb(trx);
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
findById: (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
return (tx || db)(tableName)
|
||||||
|
.where({ id } as any)
|
||||||
|
.first("*");
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find by id" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findOne: async (filter: Partial<Tables[Tname]["base"]>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const res = await (tx || db)(tableName).where(filter).first("*");
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find one" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
find: (filter: Partial<Tables[Tname]["base"]>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
return (tx || db)(tableName).where(filter);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find one" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
create: async (data: Tables[Tname]["insert"], tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [user] = await (tx || db)(tableName).insert(data).returning("*");
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Create" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateById: async (id: string, data: Tables[Tname]["update"], tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [user] = await (tx || db)(tableName)
|
||||||
|
.where({ id } as any)
|
||||||
|
.update(data as any)
|
||||||
|
.returning("*");
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Update by id" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: async (
|
||||||
|
filter: Partial<Tables[Tname]["base"]>,
|
||||||
|
data: Tables[Tname]["update"],
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const user = await (tx || db)(tableName)
|
||||||
|
.where(filter)
|
||||||
|
.update(data as any)
|
||||||
|
.returning("*");
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Update" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteById: async (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [user] = await (tx || db)(tableName)
|
||||||
|
.where({ id } as any)
|
||||||
|
.delete()
|
||||||
|
.returning("*");
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Delete by id" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: async (filter: Partial<Tables[Tname]["base"]>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const user = await (tx || db)(tableName).where(filter).delete().returning("*");
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Delete" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...(dal || {})
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
|
import fp from "fastify-plugin";
|
||||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
@@ -27,7 +28,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
|
|||||||
case AuthMode.SERVICE_ACCESS_TOKEN:
|
case AuthMode.SERVICE_ACCESS_TOKEN:
|
||||||
return { authMode: AuthMode.SERVICE_ACCESS_TOKEN, token: decodedToken } as const;
|
return { authMode: AuthMode.SERVICE_ACCESS_TOKEN, token: decodedToken } as const;
|
||||||
default:
|
default:
|
||||||
throw new UnauthorizedError({ name: "Invalid token type" });
|
return { authMode: null, token: null } as const;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,13 +42,13 @@ const getJwtIdentity = async (server: FastifyZodProvider, token: AuthModeJwtToke
|
|||||||
if (token.accessVersion !== session.accessVersion)
|
if (token.accessVersion !== session.accessVersion)
|
||||||
throw new UnauthorizedError({ name: "Stale session" });
|
throw new UnauthorizedError({ name: "Stale session" });
|
||||||
|
|
||||||
const user = await server.store.user.getUserById(session.userId);
|
const user = await server.store.user.findById(session.userId);
|
||||||
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
|
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
|
||||||
|
|
||||||
return user;
|
return { user, tokenVersionId: token.tokenVersionId };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const injectIdentity = (server: FastifyZodProvider) => {
|
export const injectIdentity = fp(async (server: FastifyZodProvider) => {
|
||||||
server.decorateRequest("auth", null);
|
server.decorateRequest("auth", null);
|
||||||
server.addHook("onRequest", async (req) => {
|
server.addHook("onRequest", async (req) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -56,8 +57,11 @@ export const injectIdentity = (server: FastifyZodProvider) => {
|
|||||||
// TODO(akhilmhdh-pg): fill in rest of auth mode logic
|
// TODO(akhilmhdh-pg): fill in rest of auth mode logic
|
||||||
switch (authMode) {
|
switch (authMode) {
|
||||||
case AuthMode.JWT: {
|
case AuthMode.JWT: {
|
||||||
const user = await getJwtIdentity(server, token as AuthModeJwtTokenPayload);
|
const { user, tokenVersionId } = await getJwtIdentity(
|
||||||
req.auth = { authMode: AuthMode.JWT, user, userId: user.id };
|
server,
|
||||||
|
token as AuthModeJwtTokenPayload
|
||||||
|
);
|
||||||
|
req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthMode.SERVICE_TOKEN:
|
case AuthMode.SERVICE_TOKEN:
|
||||||
@@ -72,4 +76,4 @@ export const injectIdentity = (server: FastifyZodProvider) => {
|
|||||||
throw new UnauthorizedError({ name: "Unknown token strategy" });
|
throw new UnauthorizedError({ name: "Unknown token strategy" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { FastifyRequest } from "fastify";
|
||||||
|
|
||||||
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
export const verifySuperAdmin = async <T extends FastifyRequest>(req: T) => {
|
||||||
|
if (!req.auth.user.superAdmin)
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
name: "Unauthorized access",
|
||||||
|
message: "Requires superadmin access"
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
export const verifyAuth =
|
export const verifyAuth =
|
||||||
<T extends FastifyRequest>(authStrats: AuthMode[]) =>
|
<T extends FastifyRequest>(authStrats: AuthMode[]) =>
|
||||||
(req: T) => {
|
async (req: T) => {
|
||||||
if (!Array.isArray(authStrats)) throw new Error("Auth strategy must be array");
|
if (!Array.isArray(authStrats)) throw new Error("Auth strategy must be array");
|
||||||
if (!req.auth)
|
if (!req.auth)
|
||||||
throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" });
|
throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" });
|
||||||
|
|||||||
@@ -1,47 +1,142 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
||||||
|
import { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
|
||||||
|
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
||||||
|
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { authDalFactory } from "@app/services/auth/auth-dal";
|
import { authDalFactory } from "@app/services/auth/auth-dal";
|
||||||
import { authLoginServiceFactory } from "@app/services/auth/auth-login-service";
|
import { authLoginServiceFactory } from "@app/services/auth/auth-login-service";
|
||||||
import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service";
|
import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service";
|
||||||
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
|
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
|
||||||
|
import { incidentContactDalFactory } from "@app/services/org/incident-contacts-dal";
|
||||||
|
import { orgDalFactory } from "@app/services/org/org-dal";
|
||||||
|
import { orgRoleDalFactory } from "@app/services/org/org-role-dal";
|
||||||
|
import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
|
||||||
|
import { orgServiceFactory } from "@app/services/org/org-service";
|
||||||
|
import { serverCfgDalFactory } from "@app/services/server-cfg/server-cfg-dal";
|
||||||
|
import { serverCfgServiceFactory } from "@app/services/server-cfg/server-cfg-service";
|
||||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { tokenDalFactory } from "@app/services/token/token-dal";
|
import { tokenDalFactory } from "@app/services/token/token-dal";
|
||||||
import { tokenServiceFactory } from "@app/services/token/token-service";
|
import { tokenServiceFactory } from "@app/services/token/token-service";
|
||||||
|
import { userDalFactory } from "@app/services/user/user-dal";
|
||||||
|
import { userServiceFactory } from "@app/services/user/user-service";
|
||||||
|
|
||||||
|
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||||
import { registerV1Routes } from "./v1";
|
import { registerV1Routes } from "./v1";
|
||||||
import { registerV2Routes } from "./v2";
|
import { registerV2Routes } from "./v2";
|
||||||
import { registerV3Routes } from "./v3";
|
import { registerV3Routes } from "./v3";
|
||||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
|
||||||
|
|
||||||
export const registerRoutes = async (
|
export const registerRoutes = async (
|
||||||
server: FastifyZodProvider,
|
server: FastifyZodProvider,
|
||||||
{ db, smtp }: { db: Knex; smtp: TSmtpService }
|
{ db, smtp: smtpService }: { db: Knex; smtp: TSmtpService }
|
||||||
) => {
|
) => {
|
||||||
// db layers
|
// db layers
|
||||||
|
const userDal = userDalFactory(db);
|
||||||
const authDal = authDalFactory(db);
|
const authDal = authDalFactory(db);
|
||||||
const authTokenDal = tokenDalFactory(db);
|
const authTokenDal = tokenDalFactory(db);
|
||||||
|
const orgDal = orgDalFactory(db);
|
||||||
|
const incidentContactDal = incidentContactDalFactory(db);
|
||||||
|
const orgRoleDal = orgRoleDalFactory(db);
|
||||||
|
const serverCfgDal = serverCfgDalFactory(db);
|
||||||
|
const apiKeyDal = apiKeyDalFactory(db);
|
||||||
|
|
||||||
|
// ee db layer ops
|
||||||
|
const permissionDal = permissionDalFactory(db);
|
||||||
|
|
||||||
|
// ee services
|
||||||
|
const permissionService = permissionServiceFactory({ permissionDal });
|
||||||
|
|
||||||
// service layers
|
// service layers
|
||||||
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
|
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
|
||||||
const loginService = authLoginServiceFactory({ authDal, smtpService: smtp, tokenService });
|
const userService = userServiceFactory({ userDal });
|
||||||
const passwordService = authPaswordServiceFactory({ tokenService, smtpService: smtp, authDal });
|
const loginService = authLoginServiceFactory({ userDal, smtpService, tokenService });
|
||||||
const signupService = authSignupServiceFactory({ tokenService, smtpService: smtp, authDal });
|
const passwordService = authPaswordServiceFactory({
|
||||||
|
tokenService,
|
||||||
|
smtpService,
|
||||||
|
authDal,
|
||||||
|
userDal
|
||||||
|
});
|
||||||
|
const orgService = orgServiceFactory({
|
||||||
|
orgRoleDal,
|
||||||
|
permissionService,
|
||||||
|
orgDal,
|
||||||
|
incidentContactDal,
|
||||||
|
tokenService,
|
||||||
|
smtpService,
|
||||||
|
userDal
|
||||||
|
});
|
||||||
|
const signupService = authSignupServiceFactory({
|
||||||
|
tokenService,
|
||||||
|
smtpService,
|
||||||
|
authDal,
|
||||||
|
userDal,
|
||||||
|
orgDal,
|
||||||
|
orgService
|
||||||
|
});
|
||||||
|
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDal });
|
||||||
|
const serverCfgService = serverCfgServiceFactory({
|
||||||
|
userDal,
|
||||||
|
authService: loginService,
|
||||||
|
serverCfgDal
|
||||||
|
});
|
||||||
|
const apiKeyService = apiKeyServiceFactory({ apiKeyDal });
|
||||||
|
|
||||||
|
await serverCfgService.initServerCfg();
|
||||||
// inject all services
|
// inject all services
|
||||||
server.decorate("services", {
|
server.decorate("services", {
|
||||||
login: loginService,
|
login: loginService,
|
||||||
password: passwordService,
|
password: passwordService,
|
||||||
signup: signupService
|
signup: signupService,
|
||||||
|
user: userService,
|
||||||
|
permission: permissionService,
|
||||||
|
org: orgService,
|
||||||
|
orgRole: orgRoleService,
|
||||||
|
serverCfg: serverCfgService,
|
||||||
|
apiKey: apiKeyService,
|
||||||
|
authToken: tokenService
|
||||||
} as FastifyZodProvider["services"]);
|
} as FastifyZodProvider["services"]);
|
||||||
|
|
||||||
server.decorate("store", {
|
server.decorate("store", {
|
||||||
user: authDal
|
user: userDal
|
||||||
} as FastifyZodProvider["store"]);
|
} as FastifyZodProvider["store"]);
|
||||||
|
|
||||||
await server.register(injectIdentity);
|
await server.register(injectIdentity);
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/status",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
date: z.date(),
|
||||||
|
message: z.literal("Ok"),
|
||||||
|
emailConfigured: z.boolean().optional(),
|
||||||
|
inviteOnlySignup: z.boolean().optional(),
|
||||||
|
redisConfigured: z.boolean().optional(),
|
||||||
|
secretScanningConfigured: z.boolean().optional()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: () => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
return {
|
||||||
|
date: new Date(),
|
||||||
|
message: "Ok" as const,
|
||||||
|
emailConfigured: appCfg.isSmtpConfigured,
|
||||||
|
inviteOnlySignup: false,
|
||||||
|
redisConfigured: false,
|
||||||
|
secretScanningConfigured: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// register routes for v1
|
// register routes for v1
|
||||||
await server.register(registerV1Routes, { prefix: "/v1" });
|
await server.register(registerV1Routes, { prefix: "/v1" });
|
||||||
await server.register(registerV2Routes, { prefix: "/v2" });
|
await server.register(registerV2Routes, { prefix: "/v2" });
|
||||||
await server.register(registerV3Routes, { prefix: "/v3" });
|
await server.register(registerV3Routes, { prefix: "/v3" });
|
||||||
|
|
||||||
|
await server.register(registerV1EERoutes, { prefix: "/ee/v1" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ServerConfigSchema, UsersSchema } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/config",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
config: ServerConfigSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: () => {
|
||||||
|
const config = server.services.serverCfg.getServerCfg();
|
||||||
|
return { config };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/config",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
allowSignUp: z.boolean().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
config: ServerConfigSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preHandler: (req) => {
|
||||||
|
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req);
|
||||||
|
verifySuperAdmin(req);
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const config = await server.services.serverCfg.updateServerCfg(req.body);
|
||||||
|
return { config };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/signup",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
email: z.string().email().trim(),
|
||||||
|
firstName: z.string().trim(),
|
||||||
|
lastName: z.string().trim().optional(),
|
||||||
|
protectedKey: z.string().trim(),
|
||||||
|
protectedKeyIV: z.string().trim(),
|
||||||
|
protectedKeyTag: z.string().trim(),
|
||||||
|
publicKey: z.string().trim(),
|
||||||
|
encryptedPrivateKey: z.string().trim(),
|
||||||
|
encryptedPrivateKeyIV: z.string().trim(),
|
||||||
|
encryptedPrivateKeyTag: z.string().trim(),
|
||||||
|
salt: z.string().trim(),
|
||||||
|
verifier: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
user: UsersSchema,
|
||||||
|
token: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const serverCfg = server.services.serverCfg.getServerCfg();
|
||||||
|
if (serverCfg.initialized)
|
||||||
|
throw new UnauthorizedError({ name: "Admin sign up", message: "Admin has been created" });
|
||||||
|
const { user, token } = await server.services.serverCfg.adminSignUp({
|
||||||
|
...req.body,
|
||||||
|
ip: req.realIp,
|
||||||
|
userAgent: req.headers["user-agent"] || ""
|
||||||
|
});
|
||||||
|
|
||||||
|
res.setCookie("jid", token.refresh, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Successfully set up admin account",
|
||||||
|
user: user.user,
|
||||||
|
token: token.access
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import {
|
||||||
|
AuthMode,
|
||||||
|
AuthModeRefreshJwtTokenPayload,
|
||||||
|
AuthTokenType
|
||||||
|
} from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/logout",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
await server.services.login.logout(req.auth.userId, req.auth.tokenVersionId);
|
||||||
|
res.cookie("jid", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED
|
||||||
|
});
|
||||||
|
return { message: "Successfully logged out" };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/token",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
token: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const refreshToken = req.cookies.jid;
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!refreshToken)
|
||||||
|
throw new BadRequestError({
|
||||||
|
name: "Auth token route",
|
||||||
|
message: "Failed to find refresh token"
|
||||||
|
});
|
||||||
|
|
||||||
|
const decodedToken = jwt.verify(
|
||||||
|
refreshToken,
|
||||||
|
appCfg.JWT_AUTH_SECRET
|
||||||
|
) as AuthModeRefreshJwtTokenPayload;
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN)
|
||||||
|
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
|
||||||
|
|
||||||
|
const tokenVersion = await server.services.authToken.getUserTokenSessionById(
|
||||||
|
decodedToken.tokenVersionId,
|
||||||
|
decodedToken.userId
|
||||||
|
);
|
||||||
|
if (!tokenVersion)
|
||||||
|
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
|
||||||
|
|
||||||
|
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
|
||||||
|
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
|
||||||
|
|
||||||
|
const token = jwt.sign(
|
||||||
|
{
|
||||||
|
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||||
|
userId: decodedToken.userId,
|
||||||
|
tokenVersionId: tokenVersion.id,
|
||||||
|
accessVersion: tokenVersion.accessVersion
|
||||||
|
},
|
||||||
|
appCfg.JWT_AUTH_SECRET,
|
||||||
|
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { token };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
|
import { registerAdminRouter } from "./admin";
|
||||||
|
import { registerAuthRoutes } from "./auth";
|
||||||
|
import { registerInviteOrgRouter } from "./invite-org";
|
||||||
|
import { registerOrgRouter } from "./organization-router";
|
||||||
import { registerPasswordRouter } from "./password-router";
|
import { registerPasswordRouter } from "./password-router";
|
||||||
|
import { registerUserActionRouter } from "./user-action-router";
|
||||||
|
import { registerUserRouter } from "./user-router";
|
||||||
|
|
||||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||||
|
await server.register(registerAuthRoutes, { prefix: "/auth" });
|
||||||
await server.register(registerPasswordRouter, { prefix: "/password" });
|
await server.register(registerPasswordRouter, { prefix: "/password" });
|
||||||
|
await server.register(registerOrgRouter, { prefix: "/organization" });
|
||||||
|
await server.register(registerAdminRouter, { prefix: "/admin" });
|
||||||
|
await server.register(registerUserRouter, { prefix: "/user" });
|
||||||
|
await server.register(registerInviteOrgRouter, { prefix: "/invite-org" });
|
||||||
|
await server.register(registerUserActionRouter, { prefix: "/user-action" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UsersSchema } from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/signup",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
inviteeEmail: z.string().trim().email(),
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
completeInviteLink: z.string().optional()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const completeInviteLink = await server.services.org.inviteUserToOrganization({
|
||||||
|
orgId: req.body.organizationId,
|
||||||
|
userId: req.auth.userId,
|
||||||
|
inviteeEmail: req.body.inviteeEmail
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
completeInviteLink,
|
||||||
|
message: `Send an invite link to ${req.body.inviteeEmail}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/verify",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
email: z.string().trim().email(),
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
code: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
token: z.string().optional(),
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { user, token } = await server.services.org.verifyUserToOrg({
|
||||||
|
orgId: req.body.organizationId,
|
||||||
|
code: req.body.code,
|
||||||
|
email: req.body.email
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Successfully verified email",
|
||||||
|
user,
|
||||||
|
token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
IncidentContactsSchema,
|
||||||
|
OrganizationsSchema,
|
||||||
|
OrgMembershipsSchema,
|
||||||
|
UserEncryptionKeysSchema,
|
||||||
|
UsersSchema
|
||||||
|
} from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organizations: OrganizationsSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organizations = await server.services.org.findAllOrganizationOfUser(req.auth.userId);
|
||||||
|
return { organizations };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organization: OrganizationsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organization = await server.services.org.findOrganizationById(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { organization };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/users",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: OrgMembershipsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
user: UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
id: true
|
||||||
|
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const users = await server.services.org.findAllOrgMembers(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { users };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(akhilmhdh-pg): missing my-workspace list
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:organizationId/name",
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({ name: z.string().trim() }),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
organization: OrganizationsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organization = await server.services.org.updateOrgName(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId,
|
||||||
|
req.body.name
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
message: "Successfully changed organization name",
|
||||||
|
organization
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/incidentContactOrg",
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
incidentContactsOrg: IncidentContactsSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const incidentContactsOrg = await req.server.services.org.findIncidentContacts(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { incidentContactsOrg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:organizationId/incidentContactOrg",
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({ email: z.string().email().trim() }),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
incidentContactsOrg: IncidentContactsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const incidentContactsOrg = await req.server.services.org.createIncidentContact(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId,
|
||||||
|
req.body.email
|
||||||
|
);
|
||||||
|
return { incidentContactsOrg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/incidentContactOrg/:incidentContactId",
|
||||||
|
schema: {
|
||||||
|
// TODO(akhilmhdh-pg): change accept id instead of email
|
||||||
|
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
incidentContactsOrg: IncidentContactsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const incidentContactsOrg = await req.server.services.org.deleteIncidentContact(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId,
|
||||||
|
req.params.incidentContactId
|
||||||
|
);
|
||||||
|
return { incidentContactsOrg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UserActionsSchema } from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserActionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
action: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
userAction: UserActionsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const userAction = await server.services.user.createUserAction(
|
||||||
|
req.auth.userId,
|
||||||
|
req.body.action
|
||||||
|
);
|
||||||
|
return { userAction, message: "Successfully recorded user action" };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
action: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
userAction: UserActionsSchema.optional().nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const userAction = await server.services.user.getUserAction(
|
||||||
|
req.auth.userId,
|
||||||
|
req.query.action
|
||||||
|
);
|
||||||
|
return { userAction };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UsersSchema } from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.user.getMe(req.auth.userId);
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { registerMfaRouter } from "./mfa-router";
|
import { registerMfaRouter } from "./mfa-router";
|
||||||
|
import { registerUserRouter } from "./user-router";
|
||||||
|
|
||||||
export const registerV2Routes = async (server: FastifyZodProvider) => {
|
export const registerV2Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerMfaRouter, { prefix: "/auth" });
|
await server.register(registerMfaRouter, { prefix: "/auth" });
|
||||||
|
await server.register(registerUserRouter, { prefix: "/users" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => {
|
|||||||
if (decodedToken.authTokenType !== AuthTokenType.MFA_TOKEN)
|
if (decodedToken.authTokenType !== AuthTokenType.MFA_TOKEN)
|
||||||
throw new Error("Unauthorized access");
|
throw new Error("Unauthorized access");
|
||||||
|
|
||||||
const user = await server.store.user.getUserById(decodedToken.userId);
|
const user = await server.store.user.findById(decodedToken.userId);
|
||||||
if (!user) throw new Error("User not found");
|
if (!user) throw new Error("User not found");
|
||||||
req.mfa = { userId: user.id, user };
|
req.mfa = { userId: user.id, user };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
OrganizationsSchema,
|
||||||
|
OrgMembershipsSchema,
|
||||||
|
UserEncryptionKeysSchema,
|
||||||
|
UsersSchema
|
||||||
|
} from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/memberships",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: OrgMembershipsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
user: UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
id: true
|
||||||
|
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const users = await server.services.org.findAllOrgMembers(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { users };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:organizationId/memberships/:membershipId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||||
|
body: z.object({
|
||||||
|
role: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
membership: OrgMembershipsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const membership = await server.services.org.updateOrgMembership({
|
||||||
|
userId: req.auth.userId,
|
||||||
|
role: req.body.role,
|
||||||
|
orgId: req.params.organizationId,
|
||||||
|
membershipId: req.params.membershipId
|
||||||
|
});
|
||||||
|
return { membership };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/memberships/:membershipId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
membership: OrgMembershipsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const membership = await server.services.org.deleteOrgMembership({
|
||||||
|
userId: req.auth.userId,
|
||||||
|
orgId: req.params.organizationId,
|
||||||
|
membershipId: req.params.membershipId
|
||||||
|
});
|
||||||
|
return { membership };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organization: OrganizationsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organization = await server.services.org.createOrganization(
|
||||||
|
req.auth.userId,
|
||||||
|
req.body.name
|
||||||
|
);
|
||||||
|
return { organization };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:organizationId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organization: OrganizationsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organization = await server.services.org.deleteOrganizationById(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.organizationId
|
||||||
|
);
|
||||||
|
return { organization };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AuthTokenSessionsSchema, OrganizationsSchema, UsersSchema } from "@app/db/schemas";
|
||||||
|
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMethod, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/me/mfa",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
isMfaEnabled: z.boolean()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.user.toggleUserMfa(req.auth.userId, req.body.isMfaEnabled);
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/me/name",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
firstName: z.string().trim(),
|
||||||
|
lastName: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.user.updateUserName(
|
||||||
|
req.auth.userId,
|
||||||
|
req.body.firstName,
|
||||||
|
req.body.lastName
|
||||||
|
);
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/me/auth-methods",
|
||||||
|
method: "PUT",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
authMethods: z.nativeEnum(AuthMethod).array().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.user.updateAuthMethods(
|
||||||
|
req.auth.userId,
|
||||||
|
req.body.authMethods
|
||||||
|
);
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/me/organizations",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organizations: OrganizationsSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organizations = await server.services.org.findAllOrganizationOfUser(req.auth.userId);
|
||||||
|
return { organizations };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/me/api-keys",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: ApiKeysSchema.omit({ secretHash: true }).array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const apiKeys = await server.services.apiKey.getMyApiKeys(req.auth.userId);
|
||||||
|
return apiKeys;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/me/api-keys",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim(),
|
||||||
|
expiresIn: z.number()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
apiKey: z.string(),
|
||||||
|
apiKeyData: ApiKeysSchema.omit({ secretHash: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const apiKeys = await server.services.apiKey.createApiKey(
|
||||||
|
req.auth.userId,
|
||||||
|
req.body.name,
|
||||||
|
req.body.expiresIn
|
||||||
|
);
|
||||||
|
return apiKeys;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/me/api-keys/:apiKeyDataId",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
apiKeyDataId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
apiKeyData: ApiKeysSchema.omit({ secretHash: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const apiKeyData = await server.services.apiKey.deleteApiKey(
|
||||||
|
req.auth.userId,
|
||||||
|
req.params.apiKeyDataId
|
||||||
|
);
|
||||||
|
return { apiKeyData };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/me/sessions",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: AuthTokenSessionsSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const sessions = await server.services.authToken.getTokenSessionByUser(req.auth.userId);
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/me/sessions",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
await server.services.authToken.revokeAllMySessions(req.auth.userId);
|
||||||
|
return {
|
||||||
|
message: "Successfully revoked all sessions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/me",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.user.getMe(req.auth.userId);
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/me",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
user: UsersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.user.deleteMe(req.auth.userId);
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { registerLoginRouter } from "./login-router";
|
import { registerLoginRouter } from "./login-router";
|
||||||
import { registerSignupRouter } from "./signup-router";
|
import { registerSignupRouter } from "./signup-router";
|
||||||
|
import { registerUserRouter } from "./user-router";
|
||||||
|
|
||||||
export const registerV3Routes = async (server: FastifyZodProvider) => {
|
export const registerV3Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerSignupRouter, { prefix: "/signup" });
|
await server.register(registerSignupRouter, { prefix: "/signup" });
|
||||||
await server.register(registerLoginRouter, { prefix: "/auth" });
|
await server.register(registerLoginRouter, { prefix: "/auth" });
|
||||||
|
await server.register(registerUserRouter, { prefix: "/users" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,4 +100,54 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { message: "Successfully set up account", user, token: accessToken };
|
return { message: "Successfully set up account", user, token: accessToken };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/complete-account/invite",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
email: z.string().email().trim(),
|
||||||
|
firstName: z.string().trim(),
|
||||||
|
lastName: z.string().trim().optional(),
|
||||||
|
protectedKey: z.string().trim(),
|
||||||
|
protectedKeyIV: z.string().trim(),
|
||||||
|
protectedKeyTag: z.string().trim(),
|
||||||
|
publicKey: z.string().trim(),
|
||||||
|
encryptedPrivateKey: z.string().trim(),
|
||||||
|
encryptedPrivateKeyIV: z.string().trim(),
|
||||||
|
encryptedPrivateKeyTag: z.string().trim(),
|
||||||
|
salt: z.string().trim(),
|
||||||
|
verifier: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
user: UsersSchema,
|
||||||
|
token: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const userAgent = req.headers["user-agent"];
|
||||||
|
if (!userAgent) throw new Error("user agent header is required");
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const { user, accessToken, refreshToken } =
|
||||||
|
await server.services.signup.completeAccountInvite({
|
||||||
|
...req.body,
|
||||||
|
ip: req.realIp,
|
||||||
|
userAgent
|
||||||
|
});
|
||||||
|
|
||||||
|
res.setCookie("jid", refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED
|
||||||
|
});
|
||||||
|
// TODO(akhilmhdh-pg): add telemetry service
|
||||||
|
|
||||||
|
return { message: "Successfully set up account", user, token: accessToken };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/me/api-keys",
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
apiKeyData: ApiKeysSchema.omit({ secretHash: true }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const apiKeyData = await server.services.apiKey.getMyApiKeys(req.auth.userId);
|
||||||
|
return { apiKeyData };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TApiKeyDalFactory = ReturnType<typeof apiKeyDalFactory>;
|
||||||
|
|
||||||
|
export const apiKeyDalFactory = (db: TDbClient) => ormify(db, TableName.ApiKey);
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
|
import { TApiKeys } from "@app/db/schemas/api-keys";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TApiKeyDalFactory } from "./api-key-dal";
|
||||||
|
|
||||||
|
type TApiKeyServiceFactoryDep = {
|
||||||
|
apiKeyDal: TApiKeyDalFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TApiKeyServiceFactory = ReturnType<typeof apiKeyServiceFactory>;
|
||||||
|
|
||||||
|
const formatApiKey = ({ secretHash, ...data }: TApiKeys) => data;
|
||||||
|
|
||||||
|
export const apiKeyServiceFactory = ({ apiKeyDal }: TApiKeyServiceFactoryDep) => {
|
||||||
|
const getMyApiKeys = async (userId: string) => {
|
||||||
|
const apiKeys = await apiKeyDal.find({ userId });
|
||||||
|
return apiKeys.map((key) => formatApiKey(key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const createApiKey = async (userId: string, name: string, expiresIn: number) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const secret = crypto.randomBytes(16).toString("hex");
|
||||||
|
const secretHash = await bcrypt.hash(secret, appCfg.SALT_ROUNDS);
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
|
||||||
|
const apiKeyData = await apiKeyDal.create({
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
expiresAt,
|
||||||
|
secretHash,
|
||||||
|
lastUsed: new Date()
|
||||||
|
});
|
||||||
|
const apiKey = `ak.${apiKeyData.id}.${secret}`;
|
||||||
|
|
||||||
|
return { apiKey, apiKeyData: formatApiKey(apiKeyData) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteApiKey = async (userId: string, apiKeyId: string) => {
|
||||||
|
const [apiKeyData] = await apiKeyDal.delete({ id: apiKeyId, userId });
|
||||||
|
if (!apiKeyData)
|
||||||
|
throw new BadRequestError({ message: "Failed to find api key", name: "delete api key" });
|
||||||
|
return formatApiKey(apiKeyData);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getMyApiKeys,
|
||||||
|
createApiKey,
|
||||||
|
deleteApiKey
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,110 +1,22 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TBackupPrivateKey, TUserEncryptionKeys, TUsers } from "@app/db/schemas";
|
import { TableName, TBackupPrivateKey } from "@app/db/schemas";
|
||||||
import { withTransaction } from "@app/lib/knex";
|
import { withTransaction } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TAuthDalFactory = ReturnType<typeof authDalFactory>;
|
export type TAuthDalFactory = ReturnType<typeof authDalFactory>;
|
||||||
|
|
||||||
export const authDalFactory = (db: TDbClient) => {
|
export const authDalFactory = (db: TDbClient) => {
|
||||||
// getters
|
|
||||||
const getUserByEmail = async (email: string): Promise<TUsers | undefined> =>
|
|
||||||
db(TableName.Users).where({ email }).select("*").first();
|
|
||||||
|
|
||||||
const getUserById = async (userId: string): Promise<TUsers | undefined> =>
|
|
||||||
db(TableName.Users).where({ id: userId }).select("*").first();
|
|
||||||
|
|
||||||
const getUserEncKeyByEmail = async (email: string) =>
|
|
||||||
db(TableName.Users)
|
|
||||||
.where({ email })
|
|
||||||
.join(
|
|
||||||
TableName.UserEncryptionKey,
|
|
||||||
`${TableName.Users}.id`,
|
|
||||||
`${TableName.UserEncryptionKey}.userId`
|
|
||||||
)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const getUserEncKeyByUserId = async (userId: string) =>
|
|
||||||
db(TableName.Users)
|
|
||||||
.where({ id: userId })
|
|
||||||
.join(
|
|
||||||
TableName.UserEncryptionKey,
|
|
||||||
`${TableName.Users}.id`,
|
|
||||||
`${TableName.UserEncryptionKey}.userId`
|
|
||||||
)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const getBackupPrivateKeyByUserId = async (userId: string) =>
|
const getBackupPrivateKeyByUserId = async (userId: string) =>
|
||||||
db(TableName.BackupPrivateKey).where({ userId }).first("*");
|
db(TableName.BackupPrivateKey).where({ userId }).first("*");
|
||||||
|
|
||||||
// all inserts and updates
|
|
||||||
const createUser = async (
|
|
||||||
email: string,
|
|
||||||
data: Partial<TUsers> = {}
|
|
||||||
): Promise<TUsers | undefined> => {
|
|
||||||
const [user] = await db(TableName.Users)
|
|
||||||
.insert({ email, ...data })
|
|
||||||
.returning("*");
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateUser = async (
|
|
||||||
email: string,
|
|
||||||
data: Partial<TUsers> = {}
|
|
||||||
): Promise<TUsers | undefined> => {
|
|
||||||
const [user] = await db(TableName.Users)
|
|
||||||
.where({ email })
|
|
||||||
.update({ ...data })
|
|
||||||
.returning("*");
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateUserById = async (
|
|
||||||
id: string,
|
|
||||||
data: Partial<TUsers> = {},
|
|
||||||
tx?: Knex
|
|
||||||
): Promise<TUsers | undefined> => {
|
|
||||||
const [user] = await (tx ? tx(TableName.Users) : db(TableName.Users))
|
|
||||||
.where({ id })
|
|
||||||
.update({ ...data })
|
|
||||||
.returning("*");
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateUserEncryptionByUserId = async (
|
|
||||||
userId: string,
|
|
||||||
data: Partial<TUserEncryptionKeys> = {},
|
|
||||||
tx?: Knex
|
|
||||||
): Promise<TUserEncryptionKeys | undefined> => {
|
|
||||||
const [userEnc] = await (tx ? tx(TableName.UserEncryptionKey) : db(TableName.UserEncryptionKey))
|
|
||||||
.where({ userId })
|
|
||||||
.update({ ...data })
|
|
||||||
.returning("*");
|
|
||||||
return userEnc;
|
|
||||||
};
|
|
||||||
|
|
||||||
// all upserts
|
|
||||||
const upsertUserEncryptionKey = async (
|
|
||||||
userId: string,
|
|
||||||
data: Partial<TUserEncryptionKeys>,
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
const [userEnc] = await (tx ? tx(TableName.UserEncryptionKey) : db(TableName.UserEncryptionKey))
|
|
||||||
// if user insert make sure to pass all required data
|
|
||||||
.insert({ userId, ...data } as TUserEncryptionKeys)
|
|
||||||
.onConflict("userId")
|
|
||||||
.merge()
|
|
||||||
.returning("*");
|
|
||||||
return userEnc;
|
|
||||||
};
|
|
||||||
|
|
||||||
const upsertBackupKey = async (
|
const upsertBackupKey = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
data: Partial<TBackupPrivateKey>,
|
data: Partial<TBackupPrivateKey>,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
): Promise<TBackupPrivateKey | undefined> => {
|
): Promise<TBackupPrivateKey | undefined> => {
|
||||||
const [backupKey] = await (tx ? tx(TableName.BackupPrivateKey) : db(TableName.BackupPrivateKey))
|
const [backupKey] = await (tx || db)(TableName.BackupPrivateKey)
|
||||||
.insert({ userId, ...data, updatedAt: new Date().toUTCString() } as TBackupPrivateKey)
|
.insert({ userId, ...data, updatedAt: new Date() } as TBackupPrivateKey)
|
||||||
.onConflict("userId")
|
.onConflict("userId")
|
||||||
.merge()
|
.merge()
|
||||||
.returning("*");
|
.returning("*");
|
||||||
@@ -112,16 +24,7 @@ export const authDalFactory = (db: TDbClient) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return withTransaction(db, {
|
return withTransaction(db, {
|
||||||
getUserByEmail,
|
|
||||||
getUserById,
|
|
||||||
getUserEncKeyByEmail,
|
|
||||||
getUserEncKeyByUserId,
|
|
||||||
getBackupPrivateKeyByUserId,
|
getBackupPrivateKeyByUserId,
|
||||||
createUser,
|
|
||||||
updateUser,
|
|
||||||
updateUserById,
|
|
||||||
updateUserEncryptionByUserId,
|
|
||||||
upsertUserEncryptionKey,
|
|
||||||
upsertBackupKey
|
upsertBackupKey
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
|||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TAuthTokenServiceFactory } from "../token/token-service";
|
import { TAuthTokenServiceFactory } from "../token/token-service";
|
||||||
import { TokenType } from "../token/token-types";
|
import { TokenType } from "../token/token-types";
|
||||||
import { TAuthDalFactory } from "./auth-dal";
|
import { TUserDalFactory } from "../user/user-dal";
|
||||||
import {
|
import {
|
||||||
TLoginClientProofDTO,
|
TLoginClientProofDTO,
|
||||||
TLoginGenServerPublicKeyDTO,
|
TLoginGenServerPublicKeyDTO,
|
||||||
@@ -25,14 +25,14 @@ const isValidProviderAuthToken = (email: string, jwtSecret: string, providerAuth
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TAuthLoginServiceFactoryDep = {
|
type TAuthLoginServiceFactoryDep = {
|
||||||
authDal: TAuthDalFactory;
|
userDal: TUserDalFactory;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuthLoginFactory = ReturnType<typeof authLoginServiceFactory>;
|
export type TAuthLoginFactory = ReturnType<typeof authLoginServiceFactory>;
|
||||||
export const authLoginServiceFactory = ({
|
export const authLoginServiceFactory = ({
|
||||||
authDal,
|
userDal,
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService
|
smtpService
|
||||||
}: TAuthLoginServiceFactoryDep) => {
|
}: TAuthLoginServiceFactoryDep) => {
|
||||||
@@ -42,14 +42,14 @@ export const authLoginServiceFactory = ({
|
|||||||
* If new device is found. Will be saved and a mail will be send
|
* If new device is found. Will be saved and a mail will be send
|
||||||
*/
|
*/
|
||||||
const updateUserDeviceSession = async (user: TUsers, ip: string, userAgent: string) => {
|
const updateUserDeviceSession = async (user: TUsers, ip: string, userAgent: string) => {
|
||||||
const devices = await UserDeviceSchema.parseAsync(JSON.parse(user.devices || "[]"));
|
const devices = await UserDeviceSchema.parseAsync(user.devices || []);
|
||||||
const isDeviceSeen = devices.some(
|
const isDeviceSeen = devices.some(
|
||||||
(device) => device.ip === ip && device.userAgent === userAgent
|
(device) => device.ip === ip && device.userAgent === userAgent
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isDeviceSeen) {
|
if (!isDeviceSeen) {
|
||||||
const newDeviceList = devices.concat([{ ip, userAgent }]);
|
const newDeviceList = devices.concat([{ ip, userAgent }]);
|
||||||
await authDal.updateUserById(user.id, { devices: JSON.stringify(newDeviceList) });
|
await userDal.updateById(user.id, { devices: JSON.stringify(newDeviceList) });
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.NewDeviceJoin,
|
template: SmtpTemplates.NewDeviceJoin,
|
||||||
subjectLine: "Successful login from new device",
|
subjectLine: "Successful login from new device",
|
||||||
@@ -68,23 +68,23 @@ export const authLoginServiceFactory = ({
|
|||||||
* Private
|
* Private
|
||||||
* Send mfa code via email
|
* Send mfa code via email
|
||||||
* */
|
* */
|
||||||
const sendUserMfaCode = async (user: TUsers) => {
|
const sendUserMfaCode = async (userId: string, email: string) => {
|
||||||
const code = await tokenService.createTokenForUser({
|
const code = await tokenService.createTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_MFA,
|
type: TokenType.TOKEN_EMAIL_MFA,
|
||||||
userId: user.id
|
userId
|
||||||
});
|
});
|
||||||
|
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.EmailMfa,
|
template: SmtpTemplates.EmailMfa,
|
||||||
subjectLine: "Infisical MFA code",
|
subjectLine: "Infisical MFA code",
|
||||||
recipients: [user.email],
|
recipients: [email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
code
|
code
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Private
|
/*
|
||||||
* Check user device and send mail if new device
|
* Check user device and send mail if new device
|
||||||
* generate the auth and refresh token. fn shared by mfa verification and login verification with mfa disabled
|
* generate the auth and refresh token. fn shared by mfa verification and login verification with mfa disabled
|
||||||
*/
|
*/
|
||||||
@@ -130,20 +130,19 @@ export const authLoginServiceFactory = ({
|
|||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
clientPublicKey
|
clientPublicKey
|
||||||
}: TLoginGenServerPublicKeyDTO) => {
|
}: TLoginGenServerPublicKeyDTO) => {
|
||||||
const user = await authDal.getUserEncKeyByEmail(email);
|
const userEnc = await userDal.findUserEncKeyByEmail(email);
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
||||||
throw new Error("Failed to find user");
|
throw new Error("Failed to find user");
|
||||||
}
|
}
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!user.authMethods?.includes(AuthMethod.EMAIL) &&
|
!userEnc.authMethods?.includes(AuthMethod.EMAIL) &&
|
||||||
!isValidProviderAuthToken(email, cfg.JWT_AUTH_SECRET, providerAuthToken)
|
!isValidProviderAuthToken(email, cfg.JWT_AUTH_SECRET, providerAuthToken)
|
||||||
) {
|
) {
|
||||||
throw new Error("Invalid authorization request");
|
throw new Error("Invalid authorization request");
|
||||||
}
|
}
|
||||||
const serverSrpKey = await generateSrpServerKey(user.salt, user.verifier);
|
const serverSrpKey = await generateSrpServerKey(userEnc.salt, userEnc.verifier);
|
||||||
const userEncKeys = await authDal.updateUserEncryptionByUserId(user.id, {
|
const userEncKeys = await userDal.updateUserEncryptionByUserId(userEnc.userId, {
|
||||||
clientPublicKey,
|
clientPublicKey,
|
||||||
serverPrivateKey: serverSrpKey.privateKey
|
serverPrivateKey: serverSrpKey.privateKey
|
||||||
});
|
});
|
||||||
@@ -161,46 +160,46 @@ export const authLoginServiceFactory = ({
|
|||||||
ip,
|
ip,
|
||||||
userAgent
|
userAgent
|
||||||
}: TLoginClientProofDTO) => {
|
}: TLoginClientProofDTO) => {
|
||||||
const user = await authDal.getUserEncKeyByEmail(email);
|
const userEnc = await userDal.findUserEncKeyByEmail(email);
|
||||||
if (!user) throw new Error("Failed to find user");
|
if (!userEnc) throw new Error("Failed to find user");
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!user.authMethods?.includes(AuthMethod.EMAIL) &&
|
!userEnc.authMethods?.includes(AuthMethod.EMAIL) &&
|
||||||
!isValidProviderAuthToken(email, cfg.JWT_AUTH_SECRET, providerAuthToken)
|
!isValidProviderAuthToken(email, cfg.JWT_AUTH_SECRET, providerAuthToken)
|
||||||
) {
|
) {
|
||||||
throw new Error("Invalid authorization request");
|
throw new Error("Invalid authorization request");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.serverPrivateKey || !user.clientPublicKey)
|
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey)
|
||||||
throw new Error("Failed to authenticate. Try again?");
|
throw new Error("Failed to authenticate. Try again?");
|
||||||
const isValidClientProof = await srpCheckClientProof(
|
const isValidClientProof = await srpCheckClientProof(
|
||||||
user.salt,
|
userEnc.salt,
|
||||||
user.verifier,
|
userEnc.verifier,
|
||||||
user.serverPrivateKey,
|
userEnc.serverPrivateKey,
|
||||||
user.clientPublicKey,
|
userEnc.clientPublicKey,
|
||||||
clientProof
|
clientProof
|
||||||
);
|
);
|
||||||
if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?");
|
if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?");
|
||||||
|
|
||||||
await authDal.updateUserEncryptionByUserId(user.id, {
|
await userDal.updateUserEncryptionByUserId(userEnc.userId, {
|
||||||
serverPrivateKey: null,
|
serverPrivateKey: null,
|
||||||
clientPublicKey: null
|
clientPublicKey: null
|
||||||
});
|
});
|
||||||
// send multi factor auth token if they it enabled
|
// send multi factor auth token if they it enabled
|
||||||
if (user.isMfaEnabled) {
|
if (userEnc.isMfaEnabled) {
|
||||||
const mfaToken = jwt.sign(
|
const mfaToken = jwt.sign(
|
||||||
{ authTokenType: AuthTokenType.MFA_TOKEN, userId: user.id },
|
{ authTokenType: AuthTokenType.MFA_TOKEN, userId: userEnc.userId },
|
||||||
cfg.JWT_AUTH_SECRET,
|
cfg.JWT_AUTH_SECRET,
|
||||||
{ expiresIn: cfg.JWT_MFA_LIFETIME }
|
{ expiresIn: cfg.JWT_MFA_LIFETIME }
|
||||||
);
|
);
|
||||||
await sendUserMfaCode(user);
|
await sendUserMfaCode(userEnc.userId, userEnc.email);
|
||||||
|
|
||||||
return { isMfaEnabled: true, token: mfaToken } as const;
|
return { isMfaEnabled: true, token: mfaToken } as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await generateUserTokens(user, ip, userAgent);
|
const token = await generateUserTokens({ ...userEnc, id: userEnc.userId }, ip, userAgent);
|
||||||
return { token, isMfaEnabled: false, user } as const;
|
return { token, isMfaEnabled: false, user: userEnc } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -208,9 +207,9 @@ export const authLoginServiceFactory = ({
|
|||||||
* saved in frontend
|
* saved in frontend
|
||||||
*/
|
*/
|
||||||
const resendMfaToken = async (userId: string) => {
|
const resendMfaToken = async (userId: string) => {
|
||||||
const user = await authDal.getUserById(userId);
|
const user = await userDal.findById(userId);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
await sendUserMfaCode(user);
|
await sendUserMfaCode(user.id, user.email);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -223,11 +222,11 @@ export const authLoginServiceFactory = ({
|
|||||||
userId,
|
userId,
|
||||||
code: mfaToken
|
code: mfaToken
|
||||||
});
|
});
|
||||||
const user = await authDal.getUserEncKeyByUserId(userId);
|
const userEnc = await userDal.findUserEncKeyByUserId(userId);
|
||||||
if (!user) throw new Error("Failed to authenticate user");
|
if (!userEnc) throw new Error("Failed to authenticate user");
|
||||||
|
|
||||||
const token = await generateUserTokens(user, ip, userAgent);
|
const token = await generateUserTokens({ ...userEnc, id: userEnc.userId }, ip, userAgent);
|
||||||
return { token, user };
|
return { token, user: userEnc };
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -243,6 +242,7 @@ export const authLoginServiceFactory = ({
|
|||||||
loginExchangeClientProof,
|
loginExchangeClientProof,
|
||||||
logout,
|
logout,
|
||||||
resendMfaToken,
|
resendMfaToken,
|
||||||
verifyMfaToken
|
verifyMfaToken,
|
||||||
|
generateUserTokens
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
|||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TAuthTokenServiceFactory } from "../token/token-service";
|
import { TAuthTokenServiceFactory } from "../token/token-service";
|
||||||
import { TokenType } from "../token/token-types";
|
import { TokenType } from "../token/token-types";
|
||||||
|
import { TUserDalFactory } from "../user/user-dal";
|
||||||
import { TAuthDalFactory } from "./auth-dal";
|
import { TAuthDalFactory } from "./auth-dal";
|
||||||
import {
|
import {
|
||||||
TChangePasswordDTO,
|
TChangePasswordDTO,
|
||||||
@@ -16,6 +17,7 @@ import { AuthTokenType } from "./auth-type";
|
|||||||
|
|
||||||
type TAuthPasswordServiceFactoryDep = {
|
type TAuthPasswordServiceFactoryDep = {
|
||||||
authDal: TAuthDalFactory;
|
authDal: TAuthDalFactory;
|
||||||
|
userDal: TUserDalFactory;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
};
|
};
|
||||||
@@ -23,6 +25,7 @@ type TAuthPasswordServiceFactoryDep = {
|
|||||||
export type TAuthPasswordFactory = ReturnType<typeof authPaswordServiceFactory>;
|
export type TAuthPasswordFactory = ReturnType<typeof authPaswordServiceFactory>;
|
||||||
export const authPaswordServiceFactory = ({
|
export const authPaswordServiceFactory = ({
|
||||||
authDal,
|
authDal,
|
||||||
|
userDal,
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService
|
smtpService
|
||||||
}: TAuthPasswordServiceFactoryDep) => {
|
}: TAuthPasswordServiceFactoryDep) => {
|
||||||
@@ -31,11 +34,11 @@ export const authPaswordServiceFactory = ({
|
|||||||
* Gets srp server user salt and server public key
|
* Gets srp server user salt and server public key
|
||||||
*/
|
*/
|
||||||
const generateServerPubKey = async (userId: string, clientPublicKey: string) => {
|
const generateServerPubKey = async (userId: string, clientPublicKey: string) => {
|
||||||
const user = await authDal.getUserEncKeyByUserId(userId);
|
const userEnc = await userDal.findUserEncKeyByUserId(userId);
|
||||||
if (!user) throw new Error("Failed to find user");
|
if (!userEnc) throw new Error("Failed to find user");
|
||||||
|
|
||||||
const serverSrpKey = await generateSrpServerKey(user.salt, user.verifier);
|
const serverSrpKey = await generateSrpServerKey(userEnc.salt, userEnc.verifier);
|
||||||
const userEncKeys = await authDal.updateUserEncryptionByUserId(user.id, {
|
const userEncKeys = await userDal.updateUserEncryptionByUserId(userEnc.userId, {
|
||||||
clientPublicKey,
|
clientPublicKey,
|
||||||
serverPrivateKey: serverSrpKey.privateKey
|
serverPrivateKey: serverSrpKey.privateKey
|
||||||
});
|
});
|
||||||
@@ -59,10 +62,10 @@ export const authPaswordServiceFactory = ({
|
|||||||
verifier,
|
verifier,
|
||||||
tokenVersionId
|
tokenVersionId
|
||||||
}: TChangePasswordDTO) => {
|
}: TChangePasswordDTO) => {
|
||||||
const userEnc = await authDal.getUserEncKeyByUserId(userId);
|
const userEnc = await userDal.findUserEncKeyByUserId(userId);
|
||||||
if (!userEnc) throw new Error("Failed to find user");
|
if (!userEnc) throw new Error("Failed to find user");
|
||||||
|
|
||||||
await authDal.updateUserEncryptionByUserId(userEnc.userId, {
|
await userDal.updateUserEncryptionByUserId(userEnc.userId, {
|
||||||
serverPrivateKey: null,
|
serverPrivateKey: null,
|
||||||
clientPublicKey: null
|
clientPublicKey: null
|
||||||
});
|
});
|
||||||
@@ -77,7 +80,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
);
|
);
|
||||||
if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?");
|
if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?");
|
||||||
|
|
||||||
await authDal.updateUserEncryptionByUserId(userId, {
|
await userDal.updateUserEncryptionByUserId(userId, {
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
@@ -100,7 +103,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
* Email password reset flow via email. Step 1 send email
|
* Email password reset flow via email. Step 1 send email
|
||||||
*/
|
*/
|
||||||
const sendPasswordResetEmail = async (email: string) => {
|
const sendPasswordResetEmail = async (email: string) => {
|
||||||
const user = await authDal.getUserByEmail(email);
|
const user = await userDal.findUserByEmail(email);
|
||||||
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
||||||
if (!user || (user && !user.isAccepted)) return;
|
if (!user || (user && !user.isAccepted)) return;
|
||||||
|
|
||||||
@@ -127,7 +130,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
* */
|
* */
|
||||||
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const user = await authDal.getUserByEmail(email);
|
const user = await userDal.findUserByEmail(email);
|
||||||
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!user || (user && !user.isAccepted)) {
|
||||||
throw new Error("Failed email verification for pass reset");
|
throw new Error("Failed email verification for pass reset");
|
||||||
@@ -166,7 +169,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
encryptedPrivateKeyTag
|
encryptedPrivateKeyTag
|
||||||
}: TResetPasswordViaBackupKeyDTO
|
}: TResetPasswordViaBackupKeyDTO
|
||||||
) => {
|
) => {
|
||||||
await authDal.updateUserEncryptionByUserId(userId, {
|
await userDal.updateUserEncryptionByUserId(userId, {
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
@@ -193,23 +196,23 @@ export const authPaswordServiceFactory = ({
|
|||||||
tag,
|
tag,
|
||||||
userId
|
userId
|
||||||
}: TCreateBackupPrivateKeyDTO) => {
|
}: TCreateBackupPrivateKeyDTO) => {
|
||||||
const user = await authDal.getUserEncKeyByUserId(userId);
|
const userEnc = await userDal.findUserEncKeyByUserId(userId);
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
||||||
throw new Error("Failed to find user");
|
throw new Error("Failed to find user");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.clientPublicKey || !user.serverPrivateKey)
|
if (!userEnc.clientPublicKey || !userEnc.serverPrivateKey)
|
||||||
throw new Error("failed to create backup key");
|
throw new Error("failed to create backup key");
|
||||||
const isValidClientProff = await srpCheckClientProof(
|
const isValidClientProff = await srpCheckClientProof(
|
||||||
user.salt,
|
userEnc.salt,
|
||||||
user.verifier,
|
userEnc.verifier,
|
||||||
user.serverPrivateKey,
|
userEnc.serverPrivateKey,
|
||||||
user.clientPublicKey,
|
userEnc.clientPublicKey,
|
||||||
clientProof
|
clientProof
|
||||||
);
|
);
|
||||||
if (!isValidClientProff) throw new Error("failed to create backup key");
|
if (!isValidClientProff) throw new Error("failed to create backup key");
|
||||||
const backup = await authDal.transaction(async (tx) => {
|
const backup = await authDal.transaction(async (tx) => {
|
||||||
const backupKey = await authDal.upsertBackupKey(user.id, {
|
const backupKey = await authDal.upsertBackupKey(userEnc.userId, {
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
@@ -217,8 +220,8 @@ export const authPaswordServiceFactory = ({
|
|||||||
verifier
|
verifier
|
||||||
});
|
});
|
||||||
|
|
||||||
await authDal.updateUserEncryptionByUserId(
|
await userDal.updateUserEncryptionByUserId(
|
||||||
user.id,
|
userEnc.userId,
|
||||||
{
|
{
|
||||||
serverPrivateKey: null,
|
serverPrivateKey: null,
|
||||||
clientPublicKey: null
|
clientPublicKey: null
|
||||||
@@ -235,7 +238,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
* Return user back up
|
* Return user back up
|
||||||
* */
|
* */
|
||||||
const getBackupPrivateKeyOfUser = async (userId: string) => {
|
const getBackupPrivateKeyOfUser = async (userId: string) => {
|
||||||
const user = await authDal.getUserEncKeyByUserId(userId);
|
const user = await userDal.findUserEncKeyByUserId(userId);
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!user || (user && !user.isAccepted)) {
|
||||||
throw new Error("Failed to find user");
|
throw new Error("Failed to find user");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { OrgMembershipStatus } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
|
|
||||||
|
import { TOrgDalFactory } from "../org/org-dal";
|
||||||
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TAuthTokenServiceFactory } from "../token/token-service";
|
import { TAuthTokenServiceFactory } from "../token/token-service";
|
||||||
import { TokenType } from "../token/token-types";
|
import { TokenType } from "../token/token-types";
|
||||||
|
import { TUserDalFactory } from "../user/user-dal";
|
||||||
import { TAuthDalFactory } from "./auth-dal";
|
import { TAuthDalFactory } from "./auth-dal";
|
||||||
import { TCompleteAccountSignupDTO } from "./auth-signup-type";
|
import { TCompleteAccountInviteDTO, TCompleteAccountSignupDTO } from "./auth-signup-type";
|
||||||
import { AuthMethod, AuthTokenType } from "./auth-type";
|
import { AuthMethod, AuthTokenType } from "./auth-type";
|
||||||
|
|
||||||
type TAuthSignupDep = {
|
type TAuthSignupDep = {
|
||||||
authDal: TAuthDalFactory;
|
authDal: TAuthDalFactory;
|
||||||
|
userDal: TUserDalFactory;
|
||||||
|
orgService: Pick<TOrgServiceFactory, "createOrganization">;
|
||||||
|
orgDal: TOrgDalFactory;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
};
|
};
|
||||||
@@ -19,8 +27,11 @@ type TAuthSignupDep = {
|
|||||||
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
||||||
export const authSignupServiceFactory = ({
|
export const authSignupServiceFactory = ({
|
||||||
authDal,
|
authDal,
|
||||||
|
userDal,
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService
|
smtpService,
|
||||||
|
orgService,
|
||||||
|
orgDal
|
||||||
}: TAuthSignupDep) => {
|
}: TAuthSignupDep) => {
|
||||||
// first step of signup. create user and send email
|
// first step of signup. create user and send email
|
||||||
const beginEmailSignupProcess = async (email: string) => {
|
const beginEmailSignupProcess = async (email: string) => {
|
||||||
@@ -29,13 +40,13 @@ export const authSignupServiceFactory = ({
|
|||||||
throw new Error("Provided a disposable email");
|
throw new Error("Provided a disposable email");
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await authDal.getUserByEmail(email);
|
let user = await userDal.findUserByEmail(email);
|
||||||
if (user && user.isAccepted) {
|
if (user && user.isAccepted) {
|
||||||
// TODO(akhilmhdh-pg): copy as old one. this needs to be changed due to security issues
|
// TODO(akhilmhdh-pg): copy as old one. this needs to be changed due to security issues
|
||||||
throw new Error("Failed to send verification code for complete account");
|
throw new Error("Failed to send verification code for complete account");
|
||||||
}
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await authDal.createUser(email, { authMethods: [AuthMethod.EMAIL] });
|
user = await userDal.create({ authMethods: [AuthMethod.EMAIL], email });
|
||||||
}
|
}
|
||||||
if (!user) throw new Error("Failed to create user");
|
if (!user) throw new Error("Failed to create user");
|
||||||
|
|
||||||
@@ -55,7 +66,7 @@ export const authSignupServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const verifyEmailSignup = async (email: string, code: string) => {
|
const verifyEmailSignup = async (email: string, code: string) => {
|
||||||
const user = await authDal.getUserByEmail(email);
|
const user = await userDal.findUserByEmail(email);
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
// TODO(akhilmhdh): copy as old one. this needs to be changed due to security issues
|
// TODO(akhilmhdh): copy as old one. this needs to be changed due to security issues
|
||||||
throw new Error("Failed to send verification code for complete account");
|
throw new Error("Failed to send verification code for complete account");
|
||||||
@@ -91,7 +102,7 @@ export const authSignupServiceFactory = ({
|
|||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
// organizationName,
|
organizationName,
|
||||||
// attributionSource,
|
// attributionSource,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
@@ -99,19 +110,15 @@ export const authSignupServiceFactory = ({
|
|||||||
ip,
|
ip,
|
||||||
userAgent
|
userAgent
|
||||||
}: TCompleteAccountSignupDTO) => {
|
}: TCompleteAccountSignupDTO) => {
|
||||||
const user = await authDal.getUserByEmail(email);
|
const user = await userDal.findUserByEmail(email);
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
throw new Error("Failed to complete account for complete user");
|
throw new Error("Failed to complete account for complete user");
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateduser = await authDal.transaction(async (tx) => {
|
const updateduser = await authDal.transaction(async (tx) => {
|
||||||
const us = await authDal.updateUserById(
|
const us = await userDal.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
||||||
user.id,
|
|
||||||
{ firstName, lastName, isAccepted: true },
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
if (!us) throw new Error("User not found");
|
if (!us) throw new Error("User not found");
|
||||||
const userEncKey = await authDal.upsertUserEncryptionKey(
|
const userEncKey = await userDal.upsertUserEncryptionKey(
|
||||||
us.id,
|
us.id,
|
||||||
{
|
{
|
||||||
salt,
|
salt,
|
||||||
@@ -129,7 +136,116 @@ export const authSignupServiceFactory = ({
|
|||||||
return { info: us, key: userEncKey };
|
return { info: us, key: userEncKey };
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): add default org memberships
|
const hasSamlEnabled = user?.authMethods?.some((authMethod) =>
|
||||||
|
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(
|
||||||
|
authMethod as AuthMethod
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasSamlEnabled) {
|
||||||
|
await orgService.createOrganization(user.id, organizationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
await orgDal.updateMembership(
|
||||||
|
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
||||||
|
{ userId: user.id, status: OrgMembershipStatus.Accepted }
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokenSession = await tokenService.getUserTokenSession({
|
||||||
|
userAgent,
|
||||||
|
ip,
|
||||||
|
userId: updateduser.info.id
|
||||||
|
});
|
||||||
|
if (!tokenSession) throw new Error("Failed to create token");
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const accessToken = jwt.sign(
|
||||||
|
{
|
||||||
|
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||||
|
userId: updateduser.info.id,
|
||||||
|
tokenVersionId: tokenSession.id,
|
||||||
|
accessVersion: tokenSession.accessVersion
|
||||||
|
},
|
||||||
|
appCfg.JWT_AUTH_SECRET,
|
||||||
|
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshToken = jwt.sign(
|
||||||
|
{
|
||||||
|
authTokenType: AuthTokenType.REFRESH_TOKEN,
|
||||||
|
userId: updateduser.info.id,
|
||||||
|
tokenVersionId: tokenSession.id,
|
||||||
|
refreshVersion: tokenSession.refreshVersion
|
||||||
|
},
|
||||||
|
appCfg.JWT_AUTH_SECRET,
|
||||||
|
{ expiresIn: appCfg.JWT_REFRESH_LIFETIME }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { user: updateduser.info, accessToken, refreshToken };
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* User signup flow when they are invited to join the org
|
||||||
|
* */
|
||||||
|
const completeAccountInvite = async ({
|
||||||
|
ip,
|
||||||
|
salt,
|
||||||
|
email,
|
||||||
|
verifier,
|
||||||
|
firstName,
|
||||||
|
publicKey,
|
||||||
|
userAgent,
|
||||||
|
lastName,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag
|
||||||
|
}: TCompleteAccountInviteDTO) => {
|
||||||
|
const user = await userDal.findUserByEmail(email);
|
||||||
|
if (!user || (user && user.isAccepted)) {
|
||||||
|
throw new Error("Failed to complete account for complete user");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [orgMembership] = await orgDal.findMembership({
|
||||||
|
inviteEmail: email,
|
||||||
|
status: OrgMembershipStatus.Invited
|
||||||
|
});
|
||||||
|
if (!orgMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find invitation for email",
|
||||||
|
name: "complete account invite"
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateduser = await authDal.transaction(async (tx) => {
|
||||||
|
const us = await userDal.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
||||||
|
if (!us) throw new Error("User not found");
|
||||||
|
const userEncKey = await userDal.upsertUserEncryptionKey(
|
||||||
|
us.id,
|
||||||
|
{
|
||||||
|
salt,
|
||||||
|
encryptionVersion: 2,
|
||||||
|
verifier,
|
||||||
|
publicKey,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await orgDal.updateMembership(
|
||||||
|
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
||||||
|
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return { info: us, key: userEncKey };
|
||||||
|
});
|
||||||
|
|
||||||
const tokenSession = await tokenService.getUserTokenSession({
|
const tokenSession = await tokenService.getUserTokenSession({
|
||||||
userAgent,
|
userAgent,
|
||||||
ip,
|
ip,
|
||||||
@@ -166,6 +282,7 @@ export const authSignupServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
beginEmailSignupProcess,
|
beginEmailSignupProcess,
|
||||||
verifyEmailSignup,
|
verifyEmailSignup,
|
||||||
completeEmailAccountSignup
|
completeEmailAccountSignup,
|
||||||
|
completeAccountInvite
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,3 +17,20 @@ export type TCompleteAccountSignupDTO = {
|
|||||||
ip: string;
|
ip: string;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TCompleteAccountInviteDTO = {
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName?: string;
|
||||||
|
protectedKey: string;
|
||||||
|
protectedKeyIV: string;
|
||||||
|
protectedKeyTag: string;
|
||||||
|
publicKey: string;
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
encryptedPrivateKeyIV: string;
|
||||||
|
encryptedPrivateKeyTag: string;
|
||||||
|
salt: string;
|
||||||
|
verifier: string;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -33,3 +33,10 @@ export type AuthModeJwtTokenPayload = {
|
|||||||
tokenVersionId: string;
|
tokenVersionId: string;
|
||||||
accessVersion: number;
|
accessVersion: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AuthModeRefreshJwtTokenPayload = {
|
||||||
|
authTokenType: AuthTokenType.REFRESH_TOKEN;
|
||||||
|
userId: string;
|
||||||
|
tokenVersionId: string;
|
||||||
|
refreshVersion: number;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName, TIncidentContacts } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
export type TIncidentContactsDalFactory = ReturnType<typeof incidentContactDalFactory>;
|
||||||
|
|
||||||
|
export const incidentContactDalFactory = (db: TDbClient) => {
|
||||||
|
const create = async (orgId: string, email: string) => {
|
||||||
|
try {
|
||||||
|
const [incidentContact] = await db(TableName.IncidentContact)
|
||||||
|
.insert({ orgId, email })
|
||||||
|
.returning("*");
|
||||||
|
return incidentContact;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ name: "Incident contact create", error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findByOrgId = async (orgId: string) => {
|
||||||
|
try {
|
||||||
|
const incidentContacts = await db(TableName.IncidentContact).where({ orgId });
|
||||||
|
return incidentContacts;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ name: "Incident contact list", error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findOne = async (orgId: string, data: Partial<TIncidentContacts>) => {
|
||||||
|
try {
|
||||||
|
const incidentContacts = await db(TableName.IncidentContact)
|
||||||
|
.where({ orgId, ...data })
|
||||||
|
.first();
|
||||||
|
return incidentContacts;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ name: "Incident contact find one", error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteById = async (id: string, orgId: string) => {
|
||||||
|
try {
|
||||||
|
const [incidentContact] = await db(TableName.IncidentContact)
|
||||||
|
.where({ orgId, id })
|
||||||
|
.delete()
|
||||||
|
.returning("*");
|
||||||
|
return incidentContact;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ name: "Incident contact delete", error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
findByOrgId,
|
||||||
|
findOne,
|
||||||
|
create,
|
||||||
|
deleteById
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import {
|
||||||
|
TableName,
|
||||||
|
TOrganizations,
|
||||||
|
TOrgMemberships,
|
||||||
|
TOrgMembershipsInsert,
|
||||||
|
TOrgMembershipsUpdate
|
||||||
|
} from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { withTransaction } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TOrgDalFactory = ReturnType<typeof orgDalFactory>;
|
||||||
|
|
||||||
|
export const orgDalFactory = (db: TDbClient) => {
|
||||||
|
const findOrgById = async (orgId: string) => {
|
||||||
|
try {
|
||||||
|
const org = await db(TableName.Organization).where({ id: orgId }).first();
|
||||||
|
return org;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find org by id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// special query
|
||||||
|
const findAllOrgsByUserId = async (userId: string): Promise<TOrganizations[]> => {
|
||||||
|
try {
|
||||||
|
const org = await db(TableName.OrgMembership)
|
||||||
|
.where({ userId })
|
||||||
|
.join(
|
||||||
|
TableName.Organization,
|
||||||
|
`${TableName.OrgMembership}.orgId`,
|
||||||
|
`${TableName.Organization}.id`
|
||||||
|
)
|
||||||
|
.select(`${TableName.Organization}.*`);
|
||||||
|
return org;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find all org by user id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// special query
|
||||||
|
const findAllOrgMembers = async (orgId: string) => {
|
||||||
|
try {
|
||||||
|
const members = await db(TableName.OrgMembership)
|
||||||
|
.where({ orgId })
|
||||||
|
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.join(
|
||||||
|
TableName.UserEncryptionKey,
|
||||||
|
`${TableName.UserEncryptionKey}.userId`,
|
||||||
|
`${TableName.Users}.id`
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("inviteEmail").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("orgId").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("role").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("roleId").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("status").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("email").withSchema(TableName.Users),
|
||||||
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
|
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
|
||||||
|
);
|
||||||
|
return members.map(({ email, firstName, lastName, userId, publicKey, ...data }) => ({
|
||||||
|
...data,
|
||||||
|
user: { email, firstName, lastName, id: userId, publicKey }
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find all org members" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async ({ name }: { name: string }, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [organization] = await (tx || db)(TableName.Organization)
|
||||||
|
.insert({ name })
|
||||||
|
.returning("*");
|
||||||
|
return organization;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Create organization" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteById = async (orgId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [org] = await (tx || db)(TableName.Organization)
|
||||||
|
.where({ id: orgId })
|
||||||
|
.delete()
|
||||||
|
.returning("*");
|
||||||
|
return org;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Update organization" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateById = async (orgId: string, data: Partial<TOrganizations>) => {
|
||||||
|
try {
|
||||||
|
const [org] = await db(TableName.Organization)
|
||||||
|
.where({ id: orgId })
|
||||||
|
.update({ ...data })
|
||||||
|
.returning("*");
|
||||||
|
return org;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Update organization" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// MEMBERSHIP OPERATIONS
|
||||||
|
// --------------------
|
||||||
|
const findMembership = async (filter: Partial<TOrgMemberships>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const membership = await (tx || db)(TableName.OrgMembership).where(filter);
|
||||||
|
return membership;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find org membership" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMembership = async (data: TOrgMembershipsInsert, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [membership] = await (tx || db)(TableName.OrgMembership).insert(data).returning("*");
|
||||||
|
return membership;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Create org membership" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMembershipById = async (id: string, data: TOrgMembershipsUpdate, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [membership] = await (tx || db)(TableName.OrgMembership)
|
||||||
|
.where({ id })
|
||||||
|
.update(data)
|
||||||
|
.returning("*");
|
||||||
|
return membership;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Update org membership" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMembership = async (
|
||||||
|
filter: Partial<TOrgMemberships>,
|
||||||
|
data: TOrgMembershipsUpdate,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const membership = await (tx || db)(TableName.OrgMembership)
|
||||||
|
.where(filter)
|
||||||
|
.update(data)
|
||||||
|
.returning("*");
|
||||||
|
return membership;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Update org memberships" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMembershipById = async (id: string, orgId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [membership] = await (tx || db)(TableName.OrgMembership)
|
||||||
|
.where({ id, orgId })
|
||||||
|
.delete()
|
||||||
|
.returning("*");
|
||||||
|
return membership;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Delete org membership" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return withTransaction(db, {
|
||||||
|
findAllOrgMembers,
|
||||||
|
findOrgById,
|
||||||
|
findAllOrgsByUserId,
|
||||||
|
create,
|
||||||
|
updateById,
|
||||||
|
deleteById,
|
||||||
|
findMembership,
|
||||||
|
createMembership,
|
||||||
|
updateMembershipById,
|
||||||
|
deleteMembershipById,
|
||||||
|
updateMembership
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName,TOrgRolesInsert, TOrgRolesUpdate } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { withTransaction } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TOrgRoleDalFactory = ReturnType<typeof orgRoleDalFactory>;
|
||||||
|
|
||||||
|
export const orgRoleDalFactory = (db: TDbClient) => {
|
||||||
|
const find = async (data: TOrgRolesUpdate, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const role = await (tx || db)(TableName.OrgRoles).where(data);
|
||||||
|
return role;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Org role find one" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findOne = async (data: TOrgRolesUpdate, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const role = await (tx || db)(TableName.OrgRoles).where(data).first();
|
||||||
|
return role;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Org role find one" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (data: TOrgRolesInsert, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [role] = await (tx || db)(TableName.OrgRoles).insert(data).returning("*");
|
||||||
|
return role;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Org role create" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateOne = async (
|
||||||
|
filter: { id: string; orgId: string },
|
||||||
|
data: TOrgRolesUpdate,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const [role] = await (tx || db)(TableName.OrgRoles).where(filter).update(data).returning("*");
|
||||||
|
return role;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Org role create" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteOne = async (filter: { id: string; orgId: string }, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [role] = await (tx || db)(TableName.OrgRoles).where(filter).delete().returning("*");
|
||||||
|
return role;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Org role create" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return withTransaction(db, {
|
||||||
|
find,
|
||||||
|
findOne,
|
||||||
|
create,
|
||||||
|
updateOne,
|
||||||
|
deleteOne
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { TOrgRolesInsert, TOrgRolesUpdate } from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
orgAdminPermissions,
|
||||||
|
orgMemberPermissions,
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "@app/ee/services/permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TOrgRoleDalFactory } from "./org-role-dal";
|
||||||
|
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
|
type TOrgRoleServiceFactoryDep = {
|
||||||
|
orgRoleDal: TOrgRoleDalFactory;
|
||||||
|
permissionService: TPermissionServiceFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TOrgRoleServiceFactory = ReturnType<typeof orgRoleServiceFactory>;
|
||||||
|
|
||||||
|
export const orgRoleServiceFactory = ({
|
||||||
|
orgRoleDal,
|
||||||
|
permissionService
|
||||||
|
}: TOrgRoleServiceFactoryDep) => {
|
||||||
|
const createRole = async (
|
||||||
|
userId: string,
|
||||||
|
orgId: string,
|
||||||
|
data: Omit<TOrgRolesInsert, "orgId">
|
||||||
|
) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Create,
|
||||||
|
OrgPermissionSubjects.Role
|
||||||
|
);
|
||||||
|
const existingRole = await orgRoleDal.findOne({ slug: data.slug, orgId });
|
||||||
|
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
|
||||||
|
|
||||||
|
const role = await orgRoleDal.create({ ...data, orgId });
|
||||||
|
return role;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRole = async (
|
||||||
|
userId: string,
|
||||||
|
orgId: string,
|
||||||
|
roleId: string,
|
||||||
|
data: Omit<TOrgRolesUpdate, "orgId">
|
||||||
|
) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Edit,
|
||||||
|
OrgPermissionSubjects.Role
|
||||||
|
);
|
||||||
|
if (data?.slug) {
|
||||||
|
const existingRole = await orgRoleDal.findOne({ slug: data.slug, orgId });
|
||||||
|
if (existingRole && existingRole.id !== roleId)
|
||||||
|
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
||||||
|
}
|
||||||
|
const updatedRole = await orgRoleDal.updateOne({ id: roleId, orgId }, { ...data });
|
||||||
|
if (!updateRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
|
||||||
|
return updatedRole;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRole = async (userId: string, orgId: string, roleId: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Delete,
|
||||||
|
OrgPermissionSubjects.Role
|
||||||
|
);
|
||||||
|
const deletedRole = await orgRoleDal.deleteOne({ id: roleId, orgId });
|
||||||
|
if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
|
||||||
|
|
||||||
|
return deletedRole;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listRoles = async (userId: string, orgId: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Read,
|
||||||
|
OrgPermissionSubjects.Role
|
||||||
|
);
|
||||||
|
const customRoles = await orgRoleDal.find({ orgId });
|
||||||
|
const roles = [
|
||||||
|
{
|
||||||
|
id: "admin",
|
||||||
|
orgId: "",
|
||||||
|
name: "Admin",
|
||||||
|
slug: "admin",
|
||||||
|
description: "Complete administration access over the organization",
|
||||||
|
permissions: packRules(orgAdminPermissions.rules),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "member",
|
||||||
|
orgId: "",
|
||||||
|
name: "Member",
|
||||||
|
slug: "member",
|
||||||
|
description: "Non-administrative role in an organization",
|
||||||
|
permissions: packRules(orgMemberPermissions.rules),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
},
|
||||||
|
...(customRoles || []).map(({ permissions, ...data }) => ({
|
||||||
|
...data,
|
||||||
|
permissions
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserPermission = async (userId: string, orgId: string) => {
|
||||||
|
const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
return { permissions: packRules(permission.rules), membership };
|
||||||
|
};
|
||||||
|
|
||||||
|
return { createRole, updateRole, deleteRole, listRoles, getUserPermission };
|
||||||
|
};
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "@app/ee/services/permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
|
|
||||||
|
import { AuthTokenType } from "../auth/auth-type";
|
||||||
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
|
import { TAuthTokenServiceFactory } from "../token/token-service";
|
||||||
|
import { TokenType } from "../token/token-types";
|
||||||
|
import { TUserDalFactory } from "../user/user-dal";
|
||||||
|
import { TIncidentContactsDalFactory } from "./incident-contacts-dal";
|
||||||
|
import { TOrgDalFactory } from "./org-dal";
|
||||||
|
import { TOrgRoleDalFactory } from "./org-role-dal";
|
||||||
|
import {
|
||||||
|
TDeleteOrgMembershipDTO,
|
||||||
|
TInviteUserToOrgDTO,
|
||||||
|
TUpdateOrgMembershipDTO,
|
||||||
|
TVerifyUserToOrgDTO
|
||||||
|
} from "./org-types";
|
||||||
|
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
type TOrgServiceFactoryDep = {
|
||||||
|
orgDal: TOrgDalFactory;
|
||||||
|
orgRoleDal: TOrgRoleDalFactory;
|
||||||
|
userDal: TUserDalFactory;
|
||||||
|
incidentContactDal: TIncidentContactsDalFactory;
|
||||||
|
smtpService: TSmtpService;
|
||||||
|
tokenService: TAuthTokenServiceFactory;
|
||||||
|
permissionService: TPermissionServiceFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
||||||
|
|
||||||
|
export const orgServiceFactory = ({
|
||||||
|
orgDal,
|
||||||
|
userDal,
|
||||||
|
orgRoleDal,
|
||||||
|
incidentContactDal,
|
||||||
|
permissionService,
|
||||||
|
smtpService,
|
||||||
|
tokenService
|
||||||
|
}: TOrgServiceFactoryDep) => {
|
||||||
|
/*
|
||||||
|
* Get organization details by the organization id
|
||||||
|
* */
|
||||||
|
const findOrganizationById = async (userId: string, orgId: string) => {
|
||||||
|
await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
const org = await orgDal.findOrgById(orgId);
|
||||||
|
if (!org)
|
||||||
|
throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
|
||||||
|
return org;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* Get all organization a user part of
|
||||||
|
* */
|
||||||
|
const findAllOrganizationOfUser = async (userId: string) => {
|
||||||
|
const orgs = await orgDal.findAllOrgsByUserId(userId);
|
||||||
|
return orgs;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* Get all workspace members
|
||||||
|
* */
|
||||||
|
const findAllOrgMembers = async (userId: string, orgId: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Read,
|
||||||
|
OrgPermissionSubjects.Member
|
||||||
|
);
|
||||||
|
|
||||||
|
const members = await orgDal.findAllOrgMembers(orgId);
|
||||||
|
return members;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* Update organization settings
|
||||||
|
* */
|
||||||
|
const updateOrgName = async (userId: string, orgId: string, name: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Edit,
|
||||||
|
OrgPermissionSubjects.Settings
|
||||||
|
);
|
||||||
|
const org = await orgDal.updateById(orgId, { name });
|
||||||
|
if (!org)
|
||||||
|
throw new BadRequestError({ name: "Org not found", message: "Organization not found" });
|
||||||
|
return org;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* Create organization
|
||||||
|
* */
|
||||||
|
const createOrganization = async (userId: string, orgName: string) => {
|
||||||
|
const organization = await orgDal.transaction(async (tx) => {
|
||||||
|
const org = await orgDal.create({ name: orgName }, tx);
|
||||||
|
await orgDal.createMembership(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
orgId: org.id,
|
||||||
|
role: OrgMembershipRole.Admin,
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return org;
|
||||||
|
});
|
||||||
|
|
||||||
|
return organization;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete organization by id
|
||||||
|
* */
|
||||||
|
const deleteOrganizationById = async (userId: string, orgId: string) => {
|
||||||
|
const { membership } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
if (membership.role !== OrgMembershipRole.Admin)
|
||||||
|
throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" });
|
||||||
|
|
||||||
|
const organization = await orgDal.deleteById(orgId);
|
||||||
|
return organization;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* Org membership management
|
||||||
|
* Not another service because it has close ties with how an org works doesn't make sense to seperate them
|
||||||
|
* */
|
||||||
|
const updateOrgMembership = async ({
|
||||||
|
role,
|
||||||
|
orgId,
|
||||||
|
userId,
|
||||||
|
membershipId
|
||||||
|
}: TUpdateOrgMembershipDTO) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Edit,
|
||||||
|
OrgPermissionSubjects.Member
|
||||||
|
);
|
||||||
|
|
||||||
|
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
|
||||||
|
if (isCustomRole) {
|
||||||
|
const customRole = await orgRoleDal.findOne({ slug: role, orgId });
|
||||||
|
if (!customRole)
|
||||||
|
throw new BadRequestError({ name: "Update membership", message: "Role not found" });
|
||||||
|
const membership = await orgDal.updateMembershipById(membershipId, {
|
||||||
|
role: OrgMembershipRole.Custom,
|
||||||
|
roleId: customRole.id
|
||||||
|
});
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
const membership = await orgDal.updateMembershipById(membershipId, { role, roleId: null });
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* Invite user to organization
|
||||||
|
*/
|
||||||
|
const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail }: TInviteUserToOrgDTO) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Create,
|
||||||
|
OrgPermissionSubjects.Member
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO(akhilmhdh-pg): SAML SSO check and licence check limit org members
|
||||||
|
const invitee = await orgDal.transaction(async (tx) => {
|
||||||
|
const inviteeUser = await userDal.findUserByEmail(inviteeEmail, tx);
|
||||||
|
if (inviteeUser) {
|
||||||
|
// if user already exist means its already part of infisical
|
||||||
|
// Thus the signup flow is not needed anymore
|
||||||
|
const [inviteeMembership] = await orgDal.findMembership(
|
||||||
|
{ orgId, userId: inviteeUser.id },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to invite an existing member of org",
|
||||||
|
name: "Invite user to org"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inviteeMembership) {
|
||||||
|
await orgDal.createMembership(
|
||||||
|
{
|
||||||
|
userId: inviteeUser.id,
|
||||||
|
inviteEmail: inviteeEmail,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: OrgMembershipStatus.Invited
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return inviteeUser;
|
||||||
|
}
|
||||||
|
const isEmailInvalid = await isDisposableEmail(inviteeEmail);
|
||||||
|
if (isEmailInvalid) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Provided a disposable email",
|
||||||
|
name: "Org invite"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// not invited before
|
||||||
|
const user = await userDal.create({ email: inviteeEmail, isAccepted: false });
|
||||||
|
await orgDal.createMembership({
|
||||||
|
inviteEmail: inviteeEmail,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: OrgMembershipStatus.Invited
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = await tokenService.createTokenForUser({
|
||||||
|
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||||
|
userId: invitee.id,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const org = await orgDal.findOrgById(orgId);
|
||||||
|
const user = await userDal.findById(userId);
|
||||||
|
const appCfg = getConfig();
|
||||||
|
await smtpService.sendMail({
|
||||||
|
template: SmtpTemplates.OrgInvite,
|
||||||
|
subjectLine: "Infisical organization invitation",
|
||||||
|
recipients: [inviteeEmail],
|
||||||
|
substitutions: {
|
||||||
|
inviterFirstName: user.firstName,
|
||||||
|
inviterEmail: user.email,
|
||||||
|
organizationName: org?.name,
|
||||||
|
email: inviteeEmail,
|
||||||
|
organizationId: org?.id.toString(),
|
||||||
|
token,
|
||||||
|
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!appCfg.isSmtpConfigured) {
|
||||||
|
return `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${inviteeEmail}&organization_id=${org?.id}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization invitation step 2: Verify that code [code] was sent to email [email] as part of
|
||||||
|
* magic link and issue a temporary signup token for user to complete setting up their account
|
||||||
|
*/
|
||||||
|
const verifyUserToOrg = async ({ orgId, email, code }: TVerifyUserToOrgDTO) => {
|
||||||
|
const user = await userDal.findUserByEmail(email);
|
||||||
|
if (!user) {
|
||||||
|
throw new BadRequestError({ message: "Invalid request", name: "Verify user to org" });
|
||||||
|
}
|
||||||
|
const [orgMembership] = await orgDal.findMembership({
|
||||||
|
userId: user.id,
|
||||||
|
status: OrgMembershipStatus.Invited,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
if (!orgMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find invitation",
|
||||||
|
name: "Verify user to org"
|
||||||
|
});
|
||||||
|
|
||||||
|
await tokenService.validateTokenForUser({
|
||||||
|
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||||
|
userId: user.id,
|
||||||
|
orgId: orgMembership.orgId,
|
||||||
|
code
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user.isAccepted) {
|
||||||
|
// this means user has already completed signup process
|
||||||
|
// isAccepted is set true when keys are exchanged
|
||||||
|
await orgDal.updateMembershipById(orgMembership.id, {
|
||||||
|
orgId,
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
});
|
||||||
|
// TODO(akhilmhdh-pg): update org licence subscription
|
||||||
|
return { user };
|
||||||
|
}
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const token = jwt.sign(
|
||||||
|
{
|
||||||
|
authTokenType: AuthTokenType.SIGNUP_TOKEN,
|
||||||
|
userId: user.id
|
||||||
|
},
|
||||||
|
appCfg.JWT_AUTH_SECRET,
|
||||||
|
{
|
||||||
|
expiresIn: appCfg.JWT_SIGNUP_LIFETIME
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { token, user };
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteOrgMembership = async ({ orgId, userId, membershipId }: TDeleteOrgMembershipDTO) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Delete,
|
||||||
|
OrgPermissionSubjects.Member
|
||||||
|
);
|
||||||
|
|
||||||
|
const membership = await orgDal.deleteMembershipById(membershipId, orgId);
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CRUD operations of incident contacts
|
||||||
|
* */
|
||||||
|
const findIncidentContacts = async (userId: string, orgId: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Read,
|
||||||
|
OrgPermissionSubjects.IncidentAccount
|
||||||
|
);
|
||||||
|
const incidentContacts = await incidentContactDal.findByOrgId(orgId);
|
||||||
|
return incidentContacts;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createIncidentContact = async (userId: string, orgId: string, email: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Create,
|
||||||
|
OrgPermissionSubjects.IncidentAccount
|
||||||
|
);
|
||||||
|
const doesIncidentContactExist = await incidentContactDal.findOne(orgId, { email });
|
||||||
|
if (doesIncidentContactExist) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Incident contact already exist",
|
||||||
|
name: "Incident contact exist"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const incidentContact = await incidentContactDal.create(orgId, email);
|
||||||
|
return incidentContact;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteIncidentContact = async (userId: string, orgId: string, id: string) => {
|
||||||
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Delete,
|
||||||
|
OrgPermissionSubjects.IncidentAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
const incidentContact = await incidentContactDal.deleteById(id, orgId);
|
||||||
|
return incidentContact;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
findOrganizationById,
|
||||||
|
findAllOrgMembers,
|
||||||
|
findAllOrganizationOfUser,
|
||||||
|
inviteUserToOrganization,
|
||||||
|
verifyUserToOrg,
|
||||||
|
updateOrgName,
|
||||||
|
createOrganization,
|
||||||
|
deleteOrganizationById,
|
||||||
|
deleteOrgMembership,
|
||||||
|
updateOrgMembership,
|
||||||
|
// incident contacts
|
||||||
|
findIncidentContacts,
|
||||||
|
createIncidentContact,
|
||||||
|
deleteIncidentContact
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export type TUpdateOrgMembershipDTO = {
|
||||||
|
userId: string;
|
||||||
|
orgId: string;
|
||||||
|
membershipId: string;
|
||||||
|
role: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteOrgMembershipDTO = {
|
||||||
|
userId: string;
|
||||||
|
orgId: string;
|
||||||
|
membershipId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TInviteUserToOrgDTO = {
|
||||||
|
userId: string;
|
||||||
|
orgId: string;
|
||||||
|
inviteeEmail: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TVerifyUserToOrgDTO = {
|
||||||
|
email: string;
|
||||||
|
orgId: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TServerCfgDalFactory = ReturnType<typeof serverCfgDalFactory>;
|
||||||
|
|
||||||
|
export const serverCfgDalFactory = (db: TDbClient) => ormify(db, TableName.ServerConfig, {});
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { TServerConfig, TServerConfigUpdate } from "@app/db/schemas";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
||||||
|
import { TUserDalFactory } from "../user/user-dal";
|
||||||
|
import { TServerCfgDalFactory } from "./server-cfg-dal";
|
||||||
|
import { TAdminSignUpDTO } from "./server-cfg-types";
|
||||||
|
|
||||||
|
type TServerCfgServiceFactoryDep = {
|
||||||
|
serverCfgDal: TServerCfgDalFactory;
|
||||||
|
userDal: TUserDalFactory;
|
||||||
|
authService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TServerCfgServiceFactory = ReturnType<typeof serverCfgServiceFactory>;
|
||||||
|
|
||||||
|
export const serverCfgServiceFactory = ({
|
||||||
|
serverCfgDal,
|
||||||
|
userDal,
|
||||||
|
authService
|
||||||
|
}: TServerCfgServiceFactoryDep) => {
|
||||||
|
let serverCfg: TServerConfig;
|
||||||
|
|
||||||
|
const initServerCfg = async () => {
|
||||||
|
serverCfg = await serverCfgDal.findOne({});
|
||||||
|
if (!serverCfg) {
|
||||||
|
const newCfg = await serverCfgDal.create({ initialized: true, allowSignUp: true });
|
||||||
|
serverCfg = newCfg;
|
||||||
|
return newCfg;
|
||||||
|
}
|
||||||
|
return serverCfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getServerCfg = () => {
|
||||||
|
if (!serverCfg)
|
||||||
|
throw new BadRequestError({ name: "Get server cfg", message: "Server cfg not initialized" });
|
||||||
|
return serverCfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateServerCfg = async (data: TServerConfigUpdate) => {
|
||||||
|
const cfg = await serverCfgDal.updateById(serverCfg.id, data);
|
||||||
|
return cfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const adminSignUp = async ({
|
||||||
|
lastName,
|
||||||
|
firstName,
|
||||||
|
salt,
|
||||||
|
email,
|
||||||
|
verifier,
|
||||||
|
publicKey,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag,
|
||||||
|
ip,
|
||||||
|
userAgent
|
||||||
|
}: TAdminSignUpDTO) => {
|
||||||
|
const existingUser = await userDal.findOne({ email });
|
||||||
|
if (!existingUser)
|
||||||
|
throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
|
||||||
|
|
||||||
|
const userInfo = await userDal.transaction(async (tx) => {
|
||||||
|
const newUser = await userDal.create(
|
||||||
|
{
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
email,
|
||||||
|
superAdmin: true
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const userEnc = await userDal.createUserEncryption(
|
||||||
|
{
|
||||||
|
salt,
|
||||||
|
encryptionVersion: 2,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag,
|
||||||
|
verifier,
|
||||||
|
userId: newUser.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return { user: newUser, enc: userEnc };
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateServerCfg({ initialized: true });
|
||||||
|
const token = await authService.generateUserTokens(userInfo.user, ip, userAgent);
|
||||||
|
// TODO(akhilmhdh-pg): telemetry service
|
||||||
|
return { token, user: userInfo };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
initServerCfg,
|
||||||
|
getServerCfg,
|
||||||
|
updateServerCfg,
|
||||||
|
adminSignUp
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export type TAdminSignUpDTO = {
|
||||||
|
email: string;
|
||||||
|
publicKey: string;
|
||||||
|
salt: string;
|
||||||
|
lastName?: string;
|
||||||
|
verifier: string;
|
||||||
|
firstName: string;
|
||||||
|
protectedKey: string;
|
||||||
|
protectedKeyIV: string;
|
||||||
|
protectedKeyTag: string;
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
encryptedPrivateKeyIV: string;
|
||||||
|
encryptedPrivateKeyTag: string;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
};
|
||||||
@@ -1,55 +1,31 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
import { TableName, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
import {
|
import { TDeleteTokenForUserDalDTO } from "./token-types";
|
||||||
TDeleteTokenForUserDalDTO,
|
|
||||||
TGetTokenForUserDalDTO,
|
|
||||||
TUpsertTokenForUserDalDTO
|
|
||||||
} from "./token-types";
|
|
||||||
|
|
||||||
export type TTokenDalConfig = {};
|
export type TTokenDalConfig = {};
|
||||||
|
|
||||||
export type TTokenDalFactory = ReturnType<typeof tokenDalFactory>;
|
export type TTokenDalFactory = ReturnType<typeof tokenDalFactory>;
|
||||||
|
|
||||||
|
// TODO(akhilmhdh-pg): wrap all with database error
|
||||||
export const tokenDalFactory = (db: TDbClient) => {
|
export const tokenDalFactory = (db: TDbClient) => {
|
||||||
const upsertTokenForUser = async ({
|
const authOrm = ormify(db, TableName.AuthTokens);
|
||||||
tokenHash,
|
|
||||||
expiresAt,
|
|
||||||
userId,
|
|
||||||
type,
|
|
||||||
triesLeft
|
|
||||||
}: TUpsertTokenForUserDalDTO): Promise<TAuthTokens | undefined> => {
|
|
||||||
const token = await db.transaction(async (tx) => {
|
|
||||||
await tx(TableName.AuthTokens).where({ userId, type }).delete().returning("*");
|
|
||||||
const [newToken] = await tx(TableName.AuthTokens)
|
|
||||||
.insert({ tokenHash, expiresAt: expiresAt.toUTCString(), type, userId, triesLeft })
|
|
||||||
.returning("*");
|
|
||||||
return newToken;
|
|
||||||
});
|
|
||||||
return token;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTokenForUser = async ({
|
const findOneTokenSession = async (
|
||||||
userId,
|
filter: Partial<TAuthTokenSessions>
|
||||||
type
|
|
||||||
}: TGetTokenForUserDalDTO): Promise<TAuthTokens | undefined> =>
|
|
||||||
db(TableName.AuthTokens).where({ userId, type }).first();
|
|
||||||
|
|
||||||
const getTokenSession = async (
|
|
||||||
userId: string,
|
|
||||||
ip: string,
|
|
||||||
userAgent: string
|
|
||||||
): Promise<TAuthTokenSessions | undefined> =>
|
): Promise<TAuthTokenSessions | undefined> =>
|
||||||
db(TableName.AuthTokenSession).where({ userId, ip, userAgent }).first();
|
db(TableName.AuthTokenSession).where(filter).first();
|
||||||
|
|
||||||
const getTokenSessionById = async (id: string, userId: string) =>
|
|
||||||
db(TableName.AuthTokenSession).where({ id, userId }).first();
|
|
||||||
|
|
||||||
const deleteTokenForUser = async ({
|
const deleteTokenForUser = async ({
|
||||||
userId,
|
userId,
|
||||||
type
|
type,
|
||||||
|
orgId
|
||||||
}: TDeleteTokenForUserDalDTO): Promise<TAuthTokens[] | undefined> =>
|
}: TDeleteTokenForUserDalDTO): Promise<TAuthTokens[] | undefined> =>
|
||||||
db(TableName.AuthTokens).where({ userId, type }).delete().returning("*");
|
db(TableName.AuthTokens).where({ userId, type, orgId }).delete().returning("*");
|
||||||
|
|
||||||
const decrementTriesField = async ({
|
const decrementTriesField = async ({
|
||||||
userId,
|
userId,
|
||||||
@@ -58,6 +34,15 @@ export const tokenDalFactory = (db: TDbClient) => {
|
|||||||
await db(TableName.AuthTokens).where({ userId, type }).decrement("triesLeft", 1);
|
await db(TableName.AuthTokens).where({ userId, type }).decrement("triesLeft", 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const sessions = await (tx || db)(TableName.AuthTokenSession).where(filter);
|
||||||
|
return sessions;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ name: "Find all token session", error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const insertTokenSession = async (
|
const insertTokenSession = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
ip: string,
|
ip: string,
|
||||||
@@ -70,13 +55,13 @@ export const tokenDalFactory = (db: TDbClient) => {
|
|||||||
userAgent,
|
userAgent,
|
||||||
accessVersion: 1,
|
accessVersion: 1,
|
||||||
refreshVersion: 1,
|
refreshVersion: 1,
|
||||||
lastUsed: new Date().toUTCString()
|
lastUsed: new Date()
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|
||||||
const incrementVersion = async (
|
const incrementTokenSessionVersion = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
sessionId: string
|
sessionId: string
|
||||||
): Promise<TAuthTokenSessions | undefined> => {
|
): Promise<TAuthTokenSessions | undefined> => {
|
||||||
@@ -88,14 +73,26 @@ export const tokenDalFactory = (db: TDbClient) => {
|
|||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteTokenSession = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const sessions = await (tx || db)(TableName.AuthTokenSession)
|
||||||
|
.where(filter)
|
||||||
|
.del()
|
||||||
|
.returning("*");
|
||||||
|
return sessions;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ name: "Delete token session", error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getTokenForUser,
|
...authOrm,
|
||||||
getTokenSessionById,
|
findTokenSessions,
|
||||||
upsertTokenForUser,
|
|
||||||
deleteTokenForUser,
|
deleteTokenForUser,
|
||||||
decrementTriesField,
|
decrementTriesField,
|
||||||
getTokenSession,
|
findOneTokenSession,
|
||||||
insertTokenSession,
|
insertTokenSession,
|
||||||
incrementVersion
|
incrementTokenSessionVersion,
|
||||||
|
deleteTokenSession
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,30 +56,37 @@ export const getTokenConfig = (tokenType: TokenType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) => {
|
export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) => {
|
||||||
const createTokenForUser = async ({ type, userId }: TCreateTokenForUserDTO) => {
|
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
|
||||||
const { token, ...tkCfg } = getTokenConfig(type);
|
const { token, ...tkCfg } = getTokenConfig(type);
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS);
|
const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS);
|
||||||
await tokenDal.upsertTokenForUser({
|
await tokenDal.transaction(async (tx) => {
|
||||||
userId,
|
await tokenDal.delete({ userId, type, orgId: orgId || null }, tx);
|
||||||
type,
|
const newToken = await tokenDal.create({
|
||||||
expiresAt: tkCfg.expiresAt,
|
tokenHash,
|
||||||
tokenHash,
|
expiresAt: tkCfg.expiresAt.toUTCString(),
|
||||||
triesLeft: tkCfg?.triesLeft
|
type,
|
||||||
|
userId,
|
||||||
|
orgId,
|
||||||
|
triesLeft: tkCfg?.triesLeft
|
||||||
|
});
|
||||||
|
return newToken;
|
||||||
});
|
});
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateTokenForUser = async ({
|
const validateTokenForUser = async ({
|
||||||
type,
|
type,
|
||||||
userId,
|
userId,
|
||||||
code
|
code,
|
||||||
|
orgId
|
||||||
}: TValidateTokenForUserDTO): Promise<TAuthTokens | undefined> => {
|
}: TValidateTokenForUserDTO): Promise<TAuthTokens | undefined> => {
|
||||||
const token = await tokenDal.getTokenForUser({ type, userId });
|
const token = await tokenDal.findOne({ type, userId, orgId: orgId || null });
|
||||||
// validate token
|
// validate token
|
||||||
if (!token) throw new Error("Failed to find token");
|
if (!token) throw new Error("Failed to find token");
|
||||||
if (token?.expiresAt && new Date(token.expiresAt) < new Date()) {
|
if (token?.expiresAt && new Date(token.expiresAt) < new Date()) {
|
||||||
await tokenDal.deleteTokenForUser({ type, userId });
|
await tokenDal.delete({ type, userId, orgId });
|
||||||
throw new Error("Token expired. Please try again");
|
throw new Error("Token expired. Please try again");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,15 +94,15 @@ export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) =
|
|||||||
if (!isValidToken) {
|
if (!isValidToken) {
|
||||||
if (token?.triesLeft) {
|
if (token?.triesLeft) {
|
||||||
if (token.triesLeft === 1) {
|
if (token.triesLeft === 1) {
|
||||||
await tokenDal.deleteTokenForUser({ type, userId });
|
await tokenDal.deleteTokenForUser({ type, userId, orgId: orgId || null });
|
||||||
} else {
|
} else {
|
||||||
await tokenDal.decrementTriesField({ type, userId });
|
await tokenDal.decrementTriesField({ type, userId, orgId: orgId || null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("Invalid token");
|
throw new Error("Invalid token");
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedToken = await tokenDal.deleteTokenForUser({ type, userId });
|
const deletedToken = await tokenDal.delete({ type, userId, orgId: orgId || null });
|
||||||
return deletedToken?.[0];
|
return deletedToken?.[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +111,7 @@ export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) =
|
|||||||
ip,
|
ip,
|
||||||
userAgent
|
userAgent
|
||||||
}: TIssueAuthTokenDTO): Promise<TAuthTokenSessions | undefined> => {
|
}: TIssueAuthTokenDTO): Promise<TAuthTokenSessions | undefined> => {
|
||||||
let session = await tokenDal.getTokenSession(userId, ip, userAgent);
|
let session = await tokenDal.findOneTokenSession({ userId, ip, userAgent });
|
||||||
if (!session) {
|
if (!session) {
|
||||||
session = await tokenDal.insertTokenSession(userId, ip, userAgent);
|
session = await tokenDal.insertTokenSession(userId, ip, userAgent);
|
||||||
}
|
}
|
||||||
@@ -112,18 +119,25 @@ export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getUserTokenSessionById = async (id: string, userId: string) =>
|
const getUserTokenSessionById = async (id: string, userId: string) =>
|
||||||
tokenDal.getTokenSessionById(id, userId);
|
tokenDal.findOneTokenSession({ id, userId });
|
||||||
|
|
||||||
const clearTokenSessionById = async (
|
const clearTokenSessionById = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
sessionId: string
|
sessionId: string
|
||||||
): Promise<TAuthTokenSessions | undefined> => tokenDal.incrementVersion(userId, sessionId);
|
): Promise<TAuthTokenSessions | undefined> =>
|
||||||
|
tokenDal.incrementTokenSessionVersion(userId, sessionId);
|
||||||
|
|
||||||
|
const getTokenSessionByUser = async (userId: string) => tokenDal.findTokenSessions({ userId });
|
||||||
|
|
||||||
|
const revokeAllMySessions = async (userId: string) => tokenDal.deleteTokenSession({ userId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createTokenForUser,
|
createTokenForUser,
|
||||||
validateTokenForUser,
|
validateTokenForUser,
|
||||||
getUserTokenSession,
|
getUserTokenSession,
|
||||||
clearTokenSessionById,
|
clearTokenSessionById,
|
||||||
getUserTokenSessionById
|
getUserTokenSessionById,
|
||||||
|
getTokenSessionByUser,
|
||||||
|
revokeAllMySessions
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ export enum TokenType {
|
|||||||
export type TCreateTokenForUserDTO = {
|
export type TCreateTokenForUserDTO = {
|
||||||
type: TokenType;
|
type: TokenType;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
orgId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreateOrgInviteTokenDTO = {
|
||||||
|
userId: string;
|
||||||
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TValidateTokenForUserDTO = {
|
export type TValidateTokenForUserDTO = {
|
||||||
type: TokenType;
|
type: TokenType;
|
||||||
code: string;
|
code: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
orgId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpsertTokenForUserDalDTO = {
|
export type TUpsertTokenForUserDalDTO = {
|
||||||
@@ -32,6 +39,7 @@ export type TGetTokenForUserDalDTO = {
|
|||||||
export type TDeleteTokenForUserDalDTO = {
|
export type TDeleteTokenForUserDalDTO = {
|
||||||
userId: string;
|
userId: string;
|
||||||
type: TokenType;
|
type: TokenType;
|
||||||
|
orgId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssueAuthTokenDTO = {
|
export type TIssueAuthTokenDTO = {
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import {
|
||||||
|
TableName,
|
||||||
|
TUserActionsInsert,
|
||||||
|
TUserActionsUpdate,
|
||||||
|
TUserEncryptionKeys,
|
||||||
|
TUserEncryptionKeysInsert,
|
||||||
|
TUserEncryptionKeysUpdate
|
||||||
|
} from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TUserDalFactory = ReturnType<typeof userDalFactory>;
|
||||||
|
|
||||||
|
export const userDalFactory = (db: TDbClient) => {
|
||||||
|
const userOrm = ormify(db, TableName.Users);
|
||||||
|
const findUserByEmail = async (email: string, tx?: Knex) => userOrm.findOne({ email }, tx);
|
||||||
|
|
||||||
|
// USER ENCRYPTION FUNCTIONS
|
||||||
|
// -------------------------
|
||||||
|
const findUserEncKeyByEmail = async (email: string) =>
|
||||||
|
db(TableName.Users)
|
||||||
|
.where({ email })
|
||||||
|
.join(
|
||||||
|
TableName.UserEncryptionKey,
|
||||||
|
`${TableName.Users}.id`,
|
||||||
|
`${TableName.UserEncryptionKey}.userId`
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
const findUserEncKeyByUserId = async (userId: string) =>
|
||||||
|
db(TableName.Users)
|
||||||
|
.where({ [`${TableName.Users}.id`]: userId })
|
||||||
|
.join(
|
||||||
|
TableName.UserEncryptionKey,
|
||||||
|
`${TableName.Users}.id`,
|
||||||
|
`${TableName.UserEncryptionKey}.userId`
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
const createUserEncryption = async (data: TUserEncryptionKeysInsert, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [userEnc] = await (tx || db)(TableName.UserEncryptionKey).insert(data).returning("*");
|
||||||
|
return userEnc;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Create user encryption" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUserEncryptionByUserId = async (
|
||||||
|
userId: string,
|
||||||
|
data: TUserEncryptionKeysUpdate,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
const [userEnc] = await (tx || db)(TableName.UserEncryptionKey)
|
||||||
|
.where({ userId })
|
||||||
|
.update({ ...data })
|
||||||
|
.returning("*");
|
||||||
|
return userEnc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const upsertUserEncryptionKey = async (
|
||||||
|
userId: string,
|
||||||
|
data: Omit<TUserEncryptionKeysUpdate, "userId">,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
const [userEnc] = await (tx ? tx(TableName.UserEncryptionKey) : db(TableName.UserEncryptionKey))
|
||||||
|
// if user insert make sure to pass all required data
|
||||||
|
.insert({ userId, ...data } as TUserEncryptionKeys)
|
||||||
|
.onConflict("userId")
|
||||||
|
.merge()
|
||||||
|
.returning("*");
|
||||||
|
return userEnc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// USER ACTION FUNCTIONS
|
||||||
|
// ---------------------
|
||||||
|
const findOneUserAction = (filter: TUserActionsUpdate, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
return (tx || db)(TableName.UserAction).where(filter).first("*");
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find one user action" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUserAction = async (data: TUserActionsInsert, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const [userAction] = await (tx || db)(TableName.UserAction).insert(data).returning("*");
|
||||||
|
return userAction;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Create user action" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...userOrm,
|
||||||
|
findUserByEmail,
|
||||||
|
findUserEncKeyByEmail,
|
||||||
|
findUserEncKeyByUserId,
|
||||||
|
updateUserEncryptionByUserId,
|
||||||
|
upsertUserEncryptionKey,
|
||||||
|
createUserEncryption,
|
||||||
|
findOneUserAction,
|
||||||
|
createUserAction
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { AuthMethod } from "../auth/auth-type";
|
||||||
|
import { TUserDalFactory } from "./user-dal";
|
||||||
|
|
||||||
|
type TUserServiceFactoryDep = {
|
||||||
|
userDal: TUserDalFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
|
||||||
|
|
||||||
|
export const userServiceFactory = ({ userDal }: TUserServiceFactoryDep) => {
|
||||||
|
const toggleUserMfa = async (userId: string, isMfaEnabled: boolean) => {
|
||||||
|
const updatedUser = await userDal.updateById(userId, {
|
||||||
|
isMfaEnabled,
|
||||||
|
mfaMethods: isMfaEnabled ? ["email"] : []
|
||||||
|
});
|
||||||
|
return updatedUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUserName = async (userId: string, firstName: string, lastName: string) => {
|
||||||
|
const updatedUser = await userDal.updateById(userId, {
|
||||||
|
firstName,
|
||||||
|
lastName
|
||||||
|
});
|
||||||
|
return updatedUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAuthMethods = async (userId: string, authMethods: AuthMethod[]) => {
|
||||||
|
const user = await userDal.findById(userId);
|
||||||
|
if (!user) throw new BadRequestError({ name: "Update auth methods" });
|
||||||
|
|
||||||
|
const hasSamlEnabled = user?.authMethods?.some((method) =>
|
||||||
|
[AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(
|
||||||
|
method as AuthMethod
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (hasSamlEnabled)
|
||||||
|
throw new BadRequestError({
|
||||||
|
name: "Update auth method",
|
||||||
|
message: "Failed to update auth methods due to SAML SSO "
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedUser = await userDal.updateById(userId, { authMethods });
|
||||||
|
return updatedUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMe = async (userId: string) => {
|
||||||
|
const user = await userDal.findUserEncKeyByUserId(userId);
|
||||||
|
if (!user) throw new BadRequestError({ message: "user not found", name: "Get Me" });
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMe = async (userId: string) => {
|
||||||
|
const user = await userDal.deleteById(userId);
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
// user actions operations
|
||||||
|
const createUserAction = async (userId: string, action: string) => {
|
||||||
|
const userAction = await userDal.transaction(async (tx) => {
|
||||||
|
const existingAction = await userDal.findOneUserAction({ action, userId }, tx);
|
||||||
|
if (existingAction) return existingAction;
|
||||||
|
return userDal.createUserAction({ action, userId }, tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
return userAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserAction = async (userId: string, action: string) => {
|
||||||
|
const userAction = await userDal.findOneUserAction({ action, userId });
|
||||||
|
return userAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
toggleUserMfa,
|
||||||
|
updateUserName,
|
||||||
|
updateAuthMethods,
|
||||||
|
deleteMe,
|
||||||
|
getMe,
|
||||||
|
createUserAction,
|
||||||
|
getUserAction
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -22,6 +22,6 @@
|
|||||||
"@server/*": ["./src/server/*"]
|
"@server/*": ["./src/server/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*","scripts/**/*"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { requireAuth, validateRequest } from "../../middleware";
|
|||||||
import { membershipOrgController } from "../../controllers/v1";
|
import { membershipOrgController } from "../../controllers/v1";
|
||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
|
// depreciated completely
|
||||||
|
// ignored for new codebase
|
||||||
router.post(
|
router.post(
|
||||||
// TODO endpoint: check dashboard
|
// TODO endpoint: check dashboard
|
||||||
"/membershipOrg/:membershipOrgId/change-role",
|
"/membershipOrg/:membershipOrgId/change-role",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ services:
|
|||||||
POSTGRES_DB: infisical
|
POSTGRES_DB: infisical
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
|
container_name: infisical-dev-api
|
||||||
build:
|
build:
|
||||||
context: ./backend-pg
|
context: ./backend-pg
|
||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const completeAccountSignup = async (details: CompleteAccountSignupDTO) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => {
|
export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => {
|
||||||
const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details);
|
const { data } = await apiRequest.post("/api/v3/signup/complete-account/invite", details);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ export const useDeleteIncidentContact = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeleteIncidentContactDTO>({
|
return useMutation<{}, {}, DeleteIncidentContactDTO>({
|
||||||
mutationFn: async ({ orgId, email }) => {
|
mutationFn: async ({ orgId, incidentContactId }) => {
|
||||||
const { data } = await apiRequest.delete(`/api/v1/organization/${orgId}/incidentContactOrg`, {
|
const { data } = await apiRequest.delete(
|
||||||
data: { email }
|
`/api/v1/organization/${orgId}/incidentContactOrg/${incidentContactId}`
|
||||||
});
|
);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { orgId }) => {
|
onSuccess: (_, { orgId }) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export type IncidentContact = {
|
|||||||
|
|
||||||
export type DeleteIncidentContactDTO = {
|
export type DeleteIncidentContactDTO = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
email: string;
|
incidentContactId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AddIncidentContactDTO = {
|
export type AddIncidentContactDTO = {
|
||||||
|
|||||||
@@ -67,13 +67,11 @@ const getUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => {
|
|||||||
if (orgId === "") return { permissions: [], membership: null };
|
if (orgId === "") return { permissions: [], membership: null };
|
||||||
|
|
||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
data: {
|
permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[];
|
||||||
permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[];
|
membership: OrgUser;
|
||||||
membership: OrgUser;
|
}>(`/api/ee/v1/organization/${orgId}/permissions`);
|
||||||
};
|
|
||||||
}>(`/api/v1/roles/organization/${orgId}/permissions`);
|
|
||||||
|
|
||||||
return data.data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =>
|
export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =>
|
||||||
|
|||||||
@@ -82,13 +82,14 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
|
|||||||
publicPaths.includes(`/${appProps.router.pathname.split("/")[1]}`) ||
|
publicPaths.includes(`/${appProps.router.pathname.split("/")[1]}`) ||
|
||||||
!Component.requireAuth
|
!Component.requireAuth
|
||||||
) {
|
) {
|
||||||
// TODO(akhilmhdh): bring back server config later
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<AuthProvider>
|
<ServerConfigProvider>
|
||||||
<Component {...pageProps} />
|
<AuthProvider>
|
||||||
</AuthProvider>
|
<Component {...pageProps} />
|
||||||
|
</AuthProvider>
|
||||||
|
</ServerConfigProvider>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
setLoginError(true);
|
setLoginError(true);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Login unsuccessful. Double-check your credentials and try again.",
|
text: "Login unsuccessful. Double-check your credentials and try again.",
|
||||||
@@ -236,7 +237,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row text-sm text-bunker-400 mt-2">
|
<div className="mt-2 flex flex-row text-sm text-bunker-400">
|
||||||
<Link href="/verify-email">
|
<Link href="/verify-email">
|
||||||
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||||
Forgot password? Recover your account
|
Forgot password? Recover your account
|
||||||
|
|||||||
+4
-4
@@ -35,12 +35,12 @@ export const OrgIncidentContactsTable = () => {
|
|||||||
|
|
||||||
const onRemoveIncidentContact = async () => {
|
const onRemoveIncidentContact = async () => {
|
||||||
try {
|
try {
|
||||||
const incidentContactEmail = (popUp?.removeContact?.data as { email: string })?.email;
|
const incidentContactId = (popUp?.removeContact?.data as { id: string })?.id;
|
||||||
|
|
||||||
if (!currentOrg?.id) return;
|
if (!currentOrg?.id) return;
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
orgId: currentOrg.id,
|
orgId: currentOrg.id,
|
||||||
email: incidentContactEmail
|
incidentContactId
|
||||||
});
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -80,7 +80,7 @@ export const OrgIncidentContactsTable = () => {
|
|||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{isLoading && <TableSkeleton columns={2} innerKey="incident-contact" />}
|
{isLoading && <TableSkeleton columns={2} innerKey="incident-contact" />}
|
||||||
{filteredContacts?.map(({ email }) => (
|
{filteredContacts?.map(({ email, id }) => (
|
||||||
<Tr key={email}>
|
<Tr key={email}>
|
||||||
<Td className="w-full">{email}</Td>
|
<Td className="w-full">{email}</Td>
|
||||||
<Td className="mr-4">
|
<Td className="mr-4">
|
||||||
@@ -92,7 +92,7 @@ export const OrgIncidentContactsTable = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
ariaLabel="delete"
|
ariaLabel="delete"
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
onClick={() => handlePopUpOpen("removeContact", { email })}
|
onClick={() => handlePopUpOpen("removeContact", { email, id })}
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
|||||||
Reference in New Issue
Block a user