diff --git a/.changeset/giant-squids-shave.md b/.changeset/giant-squids-shave.md new file mode 100644 index 000000000..04d9d8330 --- /dev/null +++ b/.changeset/giant-squids-shave.md @@ -0,0 +1,5 @@ +--- +'@lagon/serverless': patch +--- + +Fix URL format diff --git a/.changeset/old-eyes-scream.md b/.changeset/old-eyes-scream.md new file mode 100644 index 000000000..6a459d695 --- /dev/null +++ b/.changeset/old-eyes-scream.md @@ -0,0 +1,5 @@ +--- +'@lagon/serverless': patch +--- + +Fix domains assignement diff --git a/.changeset/serious-cobras-pull.md b/.changeset/serious-cobras-pull.md new file mode 100644 index 000000000..9b7bdf473 --- /dev/null +++ b/.changeset/serious-cobras-pull.md @@ -0,0 +1,5 @@ +--- +'@lagon/runtime': patch +--- + +Update rusty_v8 to 0.51 diff --git a/.changeset/sweet-kids-stare.md b/.changeset/sweet-kids-stare.md new file mode 100644 index 000000000..174d8408e --- /dev/null +++ b/.changeset/sweet-kids-stare.md @@ -0,0 +1,5 @@ +--- +'@lagon/serverless': patch +--- + +Use TLS for Redis / MySQL connections diff --git a/.changeset/thick-brooms-carry.md b/.changeset/thick-brooms-carry.md new file mode 100644 index 000000000..7f1434dbb --- /dev/null +++ b/.changeset/thick-brooms-carry.md @@ -0,0 +1,5 @@ +--- +'@lagon/runtime': patch +--- + +Load ICU data to enable i18n diff --git a/Cargo.lock b/Cargo.lock index 34e384ed7..36122a22c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2421,6 +2421,7 @@ dependencies = [ "async-trait", "combine", "itoa", + "native-tls", "percent-encoding 2.1.0", "ryu", "sha1", @@ -3239,9 +3240,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "v8" -version = "0.45.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afe785c0357a4c1b0a6464ab72899e688d26eafc63e0b626e2ce2d35b147477" +checksum = "e72791f754a6517e86d88e4521baad3a7d428ce54e266ba560b8747b2a99b946" dependencies = [ "bitflags", "fslock", diff --git a/docker/serverless.Dockerfile b/docker/serverless.Dockerfile index db8be62a1..5ac346802 100644 --- a/docker/serverless.Dockerfile +++ b/docker/serverless.Dockerfile @@ -1,7 +1,7 @@ FROM rust:1.63 as builder WORKDIR /app -COPY . . +COPY . packages/serverless/.env ./ WORKDIR /app/packages/serverless RUN cargo build --release @@ -10,6 +10,7 @@ RUN cargo build --release FROM rust:1.63 COPY --from=builder /app/target/release/lagon-serverless /usr/local/bin/lagon-serverless +COPY --from=builder /app/.env ./.env EXPOSE 4000 CMD ["lagon-serverless"] diff --git a/packages/runtime/Cargo.toml b/packages/runtime/Cargo.toml index 167622099..3573f6a9f 100644 --- a/packages/runtime/Cargo.toml +++ b/packages/runtime/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -v8 = "0.45.0" +v8 = "0.51.0" tokio = { version = "1", features = ["full"] } hyper = { version = "0.14", features = ["client", "http1", "http2", "tcp"] } diff --git a/packages/runtime/icudtl.dat b/packages/runtime/icudtl.dat new file mode 100644 index 000000000..dfa97affb Binary files /dev/null and b/packages/runtime/icudtl.dat differ diff --git a/packages/runtime/src/runtime/mod.rs b/packages/runtime/src/runtime/mod.rs index e0befde85..85ba1087a 100644 --- a/packages/runtime/src/runtime/mod.rs +++ b/packages/runtime/src/runtime/mod.rs @@ -2,7 +2,11 @@ use v8::V8; use crate::isolate::IsolateOptions; +#[repr(C, align(16))] +struct IcuData([u8; 10454784]); + static JS_RUNTIME: &str = include_str!("../../runtime.js"); +static ICU_DATA: IcuData = IcuData(*include_bytes!("../../icudtl.dat")); pub struct RuntimeOptions { allow_eval: bool, @@ -21,15 +25,19 @@ unsafe impl Sync for Runtime {} impl Runtime { pub fn new(options: RuntimeOptions) -> Self { - let platform = v8::new_default_platform(0, false).make_shared(); - V8::initialize_platform(platform); - V8::initialize(); + // Load ICU data to enable i18n, similar to Deno: + // https://github.com/denoland/deno/blob/a55b194638bcaace38917703b7d9233fb1989d44/core/runtime.rs#L223 + v8::icu::set_common_data_71(&ICU_DATA.0).unwrap(); // Disable code generation from `eval(...)` / `new Function(...)` if !options.allow_eval { V8::set_flags_from_string("--disallow-code-generation-from-strings"); } + let platform = v8::new_default_platform(0, false).make_shared(); + V8::initialize_platform(platform); + V8::initialize(); + Runtime {} } @@ -42,12 +50,6 @@ impl Runtime { } } -impl Drop for Runtime { - fn drop(&mut self) { - self.dispose(); - } -} - pub fn get_runtime_code<'a>( scope: &mut v8::HandleScope<'a, ()>, options: &IsolateOptions, diff --git a/packages/runtime/tests/runtime.rs b/packages/runtime/tests/runtime.rs index 4a7b21cae..94a801bab 100644 --- a/packages/runtime/tests/runtime.rs +++ b/packages/runtime/tests/runtime.rs @@ -3,17 +3,14 @@ use std::{collections::HashMap, sync::Once}; use lagon_runtime::{ http::{Method, Request, Response, RunResult}, isolate::{Isolate, IsolateOptions}, + runtime::{Runtime, RuntimeOptions}, }; fn setup() { static START: Once = Once::new(); START.call_once(|| { - let platform = v8::new_default_platform(0, false).make_shared(); - v8::V8::initialize_platform(platform); - v8::V8::initialize(); - - v8::V8::set_flags_from_string("--disallow-code-generation-from-strings"); + Runtime::new(RuntimeOptions::default()); }); } diff --git a/packages/serverless/.env.example b/packages/serverless/.env.example index 3339ab2de..2b8d5aa44 100644 --- a/packages/serverless/.env.example +++ b/packages/serverless/.env.example @@ -1,6 +1,5 @@ LAGON_TOKEN= LAGON_ROOT_DOMAIN=lagon.app -LAGON_WORKERS=1 LAGON_REGION= SENTRY_DSN= SENTRY_ENVIRONMENT=development diff --git a/packages/serverless/Cargo.toml b/packages/serverless/Cargo.toml index 13b11a5df..4c471a56f 100644 --- a/packages/serverless/Cargo.toml +++ b/packages/serverless/Cargo.toml @@ -11,7 +11,7 @@ flume = "0.10.14" mysql = "22.2.0" dotenv = "0.15.0" rust-s3 = "0.32" -redis = "0.21.6" +redis = { version = "0.21.6", features = ["tls"] } serde_json = "1.0" metrics = "0.20.1" metrics-exporter-prometheus = { version = "0.11.0", features = ["http-listener"] } diff --git a/packages/serverless/src/deployments/mod.rs b/packages/serverless/src/deployments/mod.rs index f38d66d04..c1cab1ec6 100644 --- a/packages/serverless/src/deployments/mod.rs +++ b/packages/serverless/src/deployments/mod.rs @@ -24,6 +24,7 @@ pub mod pubsub; pub struct Deployment { pub id: String, pub function_id: String, + pub function_name: String, pub domains: HashSet, pub assets: HashSet, pub environment_variables: HashMap, @@ -31,6 +32,28 @@ pub struct Deployment { pub timeout: usize, // in ms (MilliSeconds) } +impl Deployment { + pub fn get_domains(&self) -> Vec { + let mut domains = Vec::new(); + + domains.push(format!( + "{}.{}", + self.id, + dotenv::var("LAGON_ROOT_DOMAIN").expect("LAGON_ROOT_DOMAIN must be set") + )); + + // TODO: should only set function name and domains deployments when deployment is the production one + domains.push(format!( + "{}.{}", + self.function_name, + dotenv::var("LAGON_ROOT_DOMAIN").expect("LAGON_ROOT_DOMAIN must be set") + )); + domains.extend(self.domains.clone()); + + domains + } +} + pub async fn get_deployments( mut conn: PooledConn, bucket: Bucket, @@ -44,6 +67,7 @@ pub async fn get_deployments( SELECT Deployment.id, Function.id, + Function.name, Function.memory, Function.timeout, Domain.domain, @@ -57,7 +81,8 @@ pub async fn get_deployments( LEFT JOIN Asset ON Deployment.id = Asset.deploymentId ", - |(id, function_id, memory, timeout, domain, asset): ( + |(id, function_id, function_name, memory, timeout, domain, asset): ( + String, String, String, usize, @@ -79,6 +104,7 @@ pub async fn get_deployments( .or_insert(Deployment { id, function_id, + function_name, domains: domain .map(|domain| { let mut domains = HashSet::new(); @@ -126,7 +152,7 @@ pub async fn get_deployments( } } - for domain in deployment.domains.clone() { + for domain in deployment.get_domains() { deployments.insert(domain, deployment.clone()); } } diff --git a/packages/serverless/src/deployments/pubsub.rs b/packages/serverless/src/deployments/pubsub.rs index 454cba3dd..0f5d5b6c7 100644 --- a/packages/serverless/src/deployments/pubsub.rs +++ b/packages/serverless/src/deployments/pubsub.rs @@ -31,6 +31,7 @@ pub fn listen_pub_sub( let deployment = Deployment { id: value["deploymentId"].as_str().unwrap().to_string(), function_id: value["functionId"].as_str().unwrap().to_string(), + function_name: value["functionName"].as_str().unwrap().to_string(), assets: value["assets"] .as_array() .unwrap() @@ -59,7 +60,7 @@ pub fn listen_pub_sub( Ok(_) => { let mut deployments = deployments.write().await; - for domain in deployment.domains.clone() { + for domain in deployment.get_domains() { deployments.insert(domain, deployment.clone()); } } @@ -76,7 +77,7 @@ pub fn listen_pub_sub( Ok(_) => { let mut deployments = deployments.write().await; - for domain in deployment.domains.clone() { + for domain in deployment.get_domains() { deployments.remove(&domain); } } diff --git a/packages/serverless/src/http/mod.rs b/packages/serverless/src/http/mod.rs index c9c047be8..0d3156443 100644 --- a/packages/serverless/src/http/mod.rs +++ b/packages/serverless/src/http/mod.rs @@ -24,7 +24,11 @@ pub async fn hyper_request_to_request(request: HyperRequest) -> Request { _ => Method::GET, }; - let url = request.uri().to_string(); + let host = headers + .get("host") + .map(|host| host.to_string()) + .unwrap_or(String::new()); + let url = format!("http://{}{}", host, request.uri().to_string().as_str()); let body = body::to_bytes(request.into_body()).await.unwrap(); let body = String::from_utf8(body.to_vec()).unwrap(); diff --git a/packages/serverless/src/main.rs b/packages/serverless/src/main.rs index 421bbf91a..5bf4bb9fd 100644 --- a/packages/serverless/src/main.rs +++ b/packages/serverless/src/main.rs @@ -6,7 +6,7 @@ use lagon_runtime::isolate::{Isolate, IsolateOptions}; use lagon_runtime::runtime::{Runtime, RuntimeOptions}; use metrics::{counter, histogram, increment_counter}; use metrics_exporter_prometheus::PrometheusBuilder; -use mysql::Pool; +use mysql::{Opts, OptsBuilder, Pool, SslOpts}; use s3::creds::Credentials; use s3::Bucket; use std::collections::HashMap; @@ -62,7 +62,7 @@ async fn main() { init_logger().expect("Failed to init logger"); let runtime = Runtime::new(RuntimeOptions::default()); - let addr = SocketAddr::from(([127, 0, 0, 1], 4000)); + let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); let (request_tx, request_rx) = flume::unbounded::>(); let (response_tx, response_rx) = flume::unbounded::(); @@ -83,7 +83,12 @@ async fn main() { let url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set"); let url = url.as_str(); - let pool = Pool::new(url).unwrap(); + let opts = Opts::from_url(url).expect("Failed to parse DATABASE_URL"); + #[cfg(not(debug_assertions))] + let opts = OptsBuilder::from_opts(opts).ssl_opts(Some( + SslOpts::default().with_danger_accept_invalid_certs(true), + )); + let pool = Pool::new(opts).unwrap(); let conn = pool.get_conn().unwrap(); let bucket_name = dotenv::var("S3_BUCKET").expect("S3_BUCKET must be set"); @@ -111,6 +116,11 @@ async fn main() { loop { let hyper_request = request_rx.recv_async().await.unwrap(); + + let mut url = hyper_request.uri().to_string(); + // Remove the leading '/' from the url + url.remove(0); + let request = hyper_request_to_request(hyper_request).await; let hostname = request.headers.get("host").unwrap().clone(); @@ -118,9 +128,6 @@ async fn main() { match deployments.get(&hostname) { Some(deployment) => { - let url = &mut request.url.clone(); - url.remove(0); - let labels = vec![ ("deployment", deployment.id.clone()), ("function", deployment.function_id.clone()), @@ -129,7 +136,7 @@ async fn main() { increment_counter!("lagon_requests", &labels); counter!("lagon_bytes_in", request.len() as u64, &labels); - if let Some(asset) = deployment.assets.iter().find(|asset| *asset == url) { + if let Some(asset) = deployment.assets.iter().find(|asset| *asset == &url) { // TODO: handle read error let response = handle_asset(deployment, asset).unwrap(); let response = RunResult::Response(response); diff --git a/packages/website/package.json b/packages/website/package.json index 4777e3bbb..b2e60ee0d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -33,7 +33,7 @@ "formidable": "^2.0.1", "monaco-editor": "^0.33.0", "next": "12.1.6", - "next-auth": "^4.3.4", + "next-auth": "^4.12.2", "next-international": "^0.2.0", "node-fetch": "^3.2.10", "react": "18.1.0", diff --git a/packages/website/pages/api/auth/[...nextauth].ts b/packages/website/pages/api/auth/[...nextauth].ts index 0cdf948b7..63d456e56 100644 --- a/packages/website/pages/api/auth/[...nextauth].ts +++ b/packages/website/pages/api/auth/[...nextauth].ts @@ -9,8 +9,8 @@ export const authOptions: NextAuthOptions = { adapter: PrismaAdapter(prisma), providers: [ GithubProvider({ - clientId: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, + clientId: process.env.GITHUB_CLIENT_ID || '', + clientSecret: process.env.GITHUB_CLIENT_SECRET || '', }), ], secret: process.env.NEXTAUTH_SECRET, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5b41ff54..77730e317 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -246,7 +246,7 @@ importers: formidable: ^2.0.1 monaco-editor: ^0.33.0 next: 12.1.6 - next-auth: ^4.3.4 + next-auth: ^4.12.2 next-international: ^0.2.0 node-fetch: ^3.2.10 postcss: ^8.4.14 @@ -266,7 +266,7 @@ importers: '@headlessui/react': 1.6.5_ef5jwxihqo6n7gxfmzogljlgcm '@heroicons/react': 1.0.6_react@18.1.0 '@monaco-editor/react': 4.4.5_d24t5d2y775ybedwi37th2htxm - '@next-auth/prisma-adapter': 1.0.3_tl25cndjt3hldaanbvhdxcx6uq + '@next-auth/prisma-adapter': 1.0.3_uxqkpgmj52djk6c2xqm5mrt7ja '@prisma/client': 3.15.2_prisma@3.15.2 '@radix-ui/react-alert-dialog': 0.1.7_iupzlfzo47c7rghj3avw7kmjhq '@radix-ui/react-tabs': 0.1.5_react@18.1.0 @@ -282,7 +282,7 @@ importers: formidable: 2.0.1 monaco-editor: 0.33.0 next: 12.1.6_yz5u6557yqtbarbugy7nmayfrq - next-auth: 4.8.0_ef5jwxihqo6n7gxfmzogljlgcm + next-auth: 4.12.2_2mgcgz5qhsdxw4fgpqzqeafmkq next-international: 0.2.0_next@12.1.6+react@18.1.0 node-fetch: 3.2.10 react: 18.1.0 @@ -3478,14 +3478,14 @@ packages: '@napi-rs/simple-git-win32-x64-msvc': 0.1.8 dev: false - /@next-auth/prisma-adapter/1.0.3_tl25cndjt3hldaanbvhdxcx6uq: + /@next-auth/prisma-adapter/1.0.3_uxqkpgmj52djk6c2xqm5mrt7ja: resolution: {integrity: sha512-3Lq1cD3ytKM3EGKJZ4UZvlqshLtlPvYxLeCrUV9ifYwYlq51kmDaHjsIawlp8EbH5pE1UhlsvtlXMery7RghtA==} peerDependencies: '@prisma/client': '>=2.26.0 || >=3' next-auth: ^4.0.1 dependencies: '@prisma/client': 3.15.2_prisma@3.15.2 - next-auth: 4.8.0_ef5jwxihqo6n7gxfmzogljlgcm + next-auth: 4.12.2_2mgcgz5qhsdxw4fgpqzqeafmkq dev: false /@next/env/12.1.6: @@ -8212,7 +8212,6 @@ packages: /cookie/0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - dev: true /copy-concurrently/1.0.5: resolution: {integrity: sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==} @@ -9289,7 +9288,7 @@ packages: eslint-plugin-jsx-a11y: 6.6.0_eslint@8.19.0 eslint-plugin-react: 7.30.1_eslint@8.19.0 eslint-plugin-react-hooks: 4.6.0_eslint@8.19.0 - next: 12.1.6_ef5jwxihqo6n7gxfmzogljlgcm + next: 12.1.6_yz5u6557yqtbarbugy7nmayfrq typescript: 4.7.4 transitivePeerDependencies: - eslint-import-resolver-webpack @@ -11836,8 +11835,8 @@ packages: supports-color: 8.1.1 dev: true - /jose/4.8.3: - resolution: {integrity: sha512-7rySkpW78d8LBp4YU70Wb7+OTgE3OwAALNVZxhoIhp4Kscp+p/fBkdpxGAMKxvCAMV4QfXBU9m6l9nX/vGwd2g==} + /jose/4.10.0: + resolution: {integrity: sha512-KEhB/eLGLomWGPTb+/RNbYsTjIyx03JmbqAyIyiXBuNSa7CmNrJd5ysFhblayzs/e/vbOPMUaLnjHUMhGp4yLw==} dev: false /joycon/3.1.1: @@ -13253,10 +13252,11 @@ packages: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} dev: true - /next-auth/4.8.0_ef5jwxihqo6n7gxfmzogljlgcm: - resolution: {integrity: sha512-7vN7akNsAEqV8w1cqZf71UFYw18zy/N5y3xEQz2k3Rju34n7z3+cwdNjGS0FVCoKi/s1wiN27z9ruwlOwiQNsg==} + /next-auth/4.12.2_2mgcgz5qhsdxw4fgpqzqeafmkq: + resolution: {integrity: sha512-B25iFUIKYa2pRMWRFPIQWv84WJydqIsv6EbriNuzqNSZnxnlmpsrmJrTeMMLf+9a3qf9FG8enxDmDntmwnBkDQ==} engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0} peerDependencies: + next: ^12.2.5 nodemailer: ^6.6.5 react: ^17.0.2 || ^18 react-dom: ^17.0.2 || ^18 @@ -13266,8 +13266,9 @@ packages: dependencies: '@babel/runtime': 7.18.6 '@panva/hkdf': 1.0.2 - cookie: 0.4.2 - jose: 4.8.3 + cookie: 0.5.0 + jose: 4.10.0 + next: 12.1.6_yz5u6557yqtbarbugy7nmayfrq oauth: 0.9.15 openid-client: 5.1.8 preact: 10.8.2 @@ -13340,6 +13341,7 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + dev: false /next/12.1.6_yz5u6557yqtbarbugy7nmayfrq: resolution: {integrity: sha512-cebwKxL3/DhNKfg9tPZDQmbRKjueqykHHbgaoG4VBRH3AHQJ2HO0dbKFiS1hPhe1/qgc2d/hFeadsbPicmLD+A==} @@ -13381,7 +13383,6 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: false /nextra-theme-docs/2.0.0-beta.29_2mgcgz5qhsdxw4fgpqzqeafmkq: resolution: {integrity: sha512-2oGsuOv7sMxnsYPM6+qI7F0Rcq9cMTtClwa8MeOdn0FCtMjhxJjfeLxpDvXrELkVNOU9/Bg1SFHxHTLpt0/Xjw==} @@ -13800,7 +13801,7 @@ packages: resolution: {integrity: sha512-EPxJY6bT7YIYQEXSGxRC5flQ3GUhLy98ufdto6+BVBrFGPmwjUpy4xBcYuU/Wt9nPkO/3EgljBrr6Ezx4lp1RQ==} engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0} dependencies: - jose: 4.8.3 + jose: 4.10.0 lru-cache: 6.0.0 object-hash: 2.2.0 oidc-token-hash: 5.0.1 @@ -16472,7 +16473,6 @@ packages: dependencies: '@babel/core': 7.18.6 react: 18.1.0 - dev: false /styled-jsx/5.0.2_react@18.1.0: resolution: {integrity: sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==} @@ -16488,6 +16488,7 @@ packages: optional: true dependencies: react: 18.1.0 + dev: false /sucrase/3.23.0: resolution: {integrity: sha512-xgC1xboStzGhCnRywlBf/DLmkC+SkdAKqrNCDsxGrzM0phR5oUxoFKiQNrsc2D8wDdAm03iLbSZqjHDddo3IzQ==}