diff --git a/api/auth/auth_project.py b/api/auth/auth_project.py new file mode 100644 index 000000000..98a495bbb --- /dev/null +++ b/api/auth/auth_project.py @@ -0,0 +1,24 @@ +from fastapi import Request +from starlette import status +from starlette.exceptions import HTTPException + +import schemas +from chalicelib.core import projects +from or_dependencies import OR_context + + +class ProjectAuthorizer: + def __init__(self, project_identifier): + self.project_identifier: str = project_identifier + + async def __call__(self, request: Request) -> None: + if len(request.path_params.keys()) == 0 or request.path_params.get(self.project_identifier) is None: + return + current_user: schemas.CurrentContext = await OR_context(request) + project_identifier = request.path_params[self.project_identifier] + if (self.project_identifier == "projectId" \ + and projects.get_project(project_id=project_identifier, tenant_id=current_user.tenant_id) is None) \ + or (self.project_identifier.lower() == "projectKey" \ + and projects.get_internal_project_id(project_key=project_identifier) is None): + print("project not found") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="project not found.") diff --git a/api/chalicelib/core/authorizers.py b/api/chalicelib/core/authorizers.py index 899fd046f..5756e82ab 100644 --- a/api/chalicelib/core/authorizers.py +++ b/api/chalicelib/core/authorizers.py @@ -13,9 +13,9 @@ def jwt_authorizer(token): try: payload = jwt.decode( token[1], - "", + config("jwt_secret"), algorithms=config("jwt_algorithm"), - audience=[ f"front:default-foss"] + audience=[f"plugin:{helper.get_stage_name()}", f"front:{helper.get_stage_name()}"] ) except jwt.ExpiredSignatureError: print("! JWT Expired signature") @@ -42,7 +42,7 @@ def generate_jwt(id, tenant_id, iat, aud): payload={ "userId": id, "tenantId": tenant_id, - "exp": iat // 1000 + config("jwt_exp_delta_seconds",cast=int) + TimeUTC.get_utc_offset() // 1000, + "exp": iat // 1000 + config("jwt_exp_delta_seconds", cast=int) + TimeUTC.get_utc_offset() // 1000, "iss": config("jwt_issuer"), "iat": iat // 1000, "aud": aud diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index 3559f645a..e4ac36ad8 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -244,7 +244,8 @@ def get_project_key(project_id): where project_id =%(project_id)s AND deleted_at ISNULL;""", {"project_id": project_id}) ) - return cur.fetchone()["project_key"] + project = cur.fetchone() + return project["project_key"] if project is not None else None def get_capture_status(project_id): diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index b4ac0f869..ceada34f8 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -571,7 +571,6 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud): ) -@dev.timed def authenticate(email, password, for_change_password=False, for_plugin=False): with pg_client.PostgresClient() as cur: query = cur.mogrify( diff --git a/api/routers/base.py b/api/routers/base.py index ff7fe165f..5c665b2d1 100644 --- a/api/routers/base.py +++ b/api/routers/base.py @@ -2,11 +2,13 @@ from fastapi import APIRouter, Depends from auth.auth_apikey import APIKeyAuth from auth.auth_jwt import JWTAuth +from auth.auth_project import ProjectAuthorizer from or_dependencies import ORRoute def get_routers() -> (APIRouter, APIRouter, APIRouter): public_app = APIRouter(route_class=ORRoute) - app = APIRouter(dependencies=[Depends(JWTAuth())], route_class=ORRoute) - app_apikey = APIRouter(dependencies=[Depends(APIKeyAuth())], route_class=ORRoute) + app = APIRouter(dependencies=[Depends(JWTAuth()), Depends(ProjectAuthorizer("projectId"))], route_class=ORRoute) + app_apikey = APIRouter(dependencies=[Depends(APIKeyAuth()), Depends(ProjectAuthorizer("projectKey"))], + route_class=ORRoute) return public_app, app, app_apikey diff --git a/ee/api/.gitignore b/ee/api/.gitignore index 488fab072..0c649b68e 100644 --- a/ee/api/.gitignore +++ b/ee/api/.gitignore @@ -242,6 +242,7 @@ Pipfile /auth/auth_apikey.py /auth/auth_jwt.py /build.sh +/routers/base.py /routers/core.py /routers/crons/core_crons.py /routers/subs/dashboard.py diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index 3072f55a0..0f2b62cc9 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -257,7 +257,8 @@ def get_project_key(project_id): where project_id =%(project_id)s AND deleted_at ISNULL;""", {"project_id": project_id}) ) - return cur.fetchone()["project_key"] + project = cur.fetchone() + return project["project_key"] if project is not None else None def get_capture_status(project_id): diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index 9ca77c1ea..b70f6a269 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -632,7 +632,6 @@ def change_jwt_iat(user_id): return cur.fetchone().get("jwt_iat") -@dev.timed def authenticate(email, password, for_change_password=False, for_plugin=False): with pg_client.PostgresClient() as cur: query = cur.mogrify( diff --git a/ee/api/routers/base.py b/ee/api/routers/base.py deleted file mode 100644 index 5c665b2d1..000000000 --- a/ee/api/routers/base.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import APIRouter, Depends - -from auth.auth_apikey import APIKeyAuth -from auth.auth_jwt import JWTAuth -from auth.auth_project import ProjectAuthorizer -from or_dependencies import ORRoute - - -def get_routers() -> (APIRouter, APIRouter, APIRouter): - public_app = APIRouter(route_class=ORRoute) - app = APIRouter(dependencies=[Depends(JWTAuth()), Depends(ProjectAuthorizer("projectId"))], route_class=ORRoute) - app_apikey = APIRouter(dependencies=[Depends(APIKeyAuth()), Depends(ProjectAuthorizer("projectKey"))], - route_class=ORRoute) - return public_app, app, app_apikey diff --git a/ee/utilities/.gitignore b/ee/utilities/.gitignore index fc05191e0..2dd72c808 100644 --- a/ee/utilities/.gitignore +++ b/ee/utilities/.gitignore @@ -10,4 +10,5 @@ build.sh servers/peerjs-server.js servers/sourcemaps-handler.js servers/sourcemaps-server.js -#servers/websocket.js \ No newline at end of file +#servers/websocket.js +/utils/dump.js diff --git a/ee/utilities/server.js b/ee/utilities/server.js index d049faa19..8f5a34d7b 100644 --- a/ee/utilities/server.js +++ b/ee/utilities/server.js @@ -1,8 +1,9 @@ -var sourcemapsReaderServer = require('./servers/sourcemaps-server'); -var {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server'); -var express = require('express'); +const dumps = require('./utils/dump'); +const sourcemapsReaderServer = require('./servers/sourcemaps-server'); +const {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server'); +const express = require('express'); const {ExpressPeerServer} = require('peer'); -var socket; +let socket; if (process.env.redis === "true") { console.log("Using Redis"); socket = require("./servers/websocket-cluster"); @@ -13,7 +14,7 @@ if (process.env.redis === "true") { const HOST = '0.0.0.0'; const PORT = 9000; -var app = express(); +const app = express(); let debug = process.env.debug === "1" || false; const request_logger = (identity) => { @@ -50,6 +51,8 @@ peerServer.on('disconnect', peerDisconnect); peerServer.on('error', peerError); app.use('/', peerServer); app.enable('trust proxy'); +app.get('/heapdump', dumps.sendHeapSnapshot); +app.get('/heapdump/save', dumps.saveHeapSnapshot); if (process.env.uws !== "true") { var wsapp = express(); @@ -125,4 +128,5 @@ if (process.env.uws !== "true") { // process.exit(1); }); module.exports = {uapp, server}; -} \ No newline at end of file +} +console.log(`Heapdump enabled. Send a request to "/heapdump" to download a heapdump,\nor "/heapdump/save" to only generate a heapdump.`); \ No newline at end of file diff --git a/utilities/server.js b/utilities/server.js index 661ef081c..fcbdb5c62 100644 --- a/utilities/server.js +++ b/utilities/server.js @@ -1,14 +1,15 @@ -var sourcemapsReaderServer = require('./servers/sourcemaps-server'); -var {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server'); -var express = require('express'); +const dumps = require('./utils/dump'); +const sourcemapsReaderServer = require('./servers/sourcemaps-server'); +const {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server'); +const express = require('express'); const {ExpressPeerServer} = require('peer'); const socket = require("./servers/websocket"); const HOST = '0.0.0.0'; const PORT = 9000; -var app = express(); -var wsapp = express(); +const app = express(); +const wsapp = express(); let debug = process.env.debug === "1" || false; const request_logger = (identity) => { return (req, res, next) => { @@ -29,6 +30,9 @@ app.use('/sourcemaps', sourcemapsReaderServer); app.use('/assist', peerRouter); wsapp.use('/assist', socket.wsRouter); +app.get('/heapdump', dumps.sendHeapSnapshot); +app.get('/heapdump/save', dumps.saveHeapSnapshot); + const server = app.listen(PORT, HOST, () => { console.log(`App listening on http://${HOST}:${PORT}`); console.log('Press Ctrl+C to quit.'); @@ -51,3 +55,4 @@ app.enable('trust proxy'); wsapp.enable('trust proxy'); socket.start(wsserver); module.exports = {wsserver, server}; +console.log(`Heapdump enabled. Send a request to "/heapdump" to download a heapdump,\nor "/heapdump/save" to only generate a heapdump.`); \ No newline at end of file diff --git a/utilities/utils/dump.js b/utilities/utils/dump.js new file mode 100644 index 000000000..c22cffc3d --- /dev/null +++ b/utilities/utils/dump.js @@ -0,0 +1,50 @@ +const fs = require('fs'); +const v8 = require('v8'); + +const location = '/tmp/'; + +async function createHeapSnapshot() { + const fileName = `${Date.now()}.heapsnapshot`; + await fs.promises.writeFile( + location + fileName, + v8.getHeapSnapshot() + ); + return fileName; +} + + +async function sendHeapSnapshot(req, res) { + const fileName = await createHeapSnapshot(); + const file = fs.readFileSync(location + fileName, 'binary'); + const stat = fs.statSync(location + fileName); + + res.setHeader('Content-Length', stat.size); + res.setHeader('Content-Type', 'audio/mpeg'); + res.setHeader('Content-Disposition', 'attachment; filename=' + fileName); + res.write(file, 'binary'); + res.end(); + try { + fs.unlinkSync(location + fileName) + } catch (err) { + console.error("error while deleting heapsnapshot file"); + console.error(err); + } +} + +process.on('USR2', () => { + console.info('USR2 signal received.'); +}); + +async function saveHeapSnapshot(req, res) { + const fileName = await createHeapSnapshot(); + const path = location + fileName; + console.log(`Heapdump saved to ${path}`) + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({path})); +} + +process.on('USR2', () => { + console.info('USR2 signal received.'); +}); +module.exports = {sendHeapSnapshot, saveHeapSnapshot} \ No newline at end of file