From 32947145129332e1ced58d54ebd2a1d6d0610d2e Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Thu, 6 Jul 2023 11:41:18 +0100 Subject: [PATCH 01/25] login with browser using nonce --- Cargo.lock | 85 + lib/cli/Cargo.toml | 4 + lib/cli/src/cli.rs | 7 +- lib/cli/src/commands.rs | 5 +- lib/cli/src/commands/login_with_browser.rs | 256 ++ .../graphql/mutations/new_nonce.graphql | 9 + lib/registry/graphql/schema.graphql | 3788 ++++++++++------- lib/registry/src/api.rs | 31 +- lib/registry/src/graphql/mutations.rs | 8 + lib/registry/src/types.rs | 17 + 10 files changed, 2706 insertions(+), 1504 deletions(-) create mode 100644 lib/cli/src/commands/login_with_browser.rs create mode 100644 lib/registry/graphql/mutations/new_nonce.graphql diff --git a/Cargo.lock b/Cargo.lock index 0d196a97b6e..8ef4a8aeb1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,6 +254,55 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.67" @@ -2461,6 +2510,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "md5" version = "0.7.0" @@ -2693,6 +2748,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "normpath" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2789,6 +2853,17 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "opener" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788" +dependencies = [ + "bstr 1.5.0", + "normpath", + "winapi", +] + [[package]] name = "openssl" version = "0.10.55" @@ -4309,6 +4384,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tap" version = "1.0.1" @@ -5607,6 +5688,7 @@ dependencies = [ "assert_cmd 2.0.11", "async-trait", "atty", + "axum", "bytes", "bytesize", "cargo_metadata", @@ -5626,6 +5708,7 @@ dependencies = [ "log", "object 0.30.4", "once_cell", + "opener", "pathdiff", "predicates 3.0.3", "pretty_assertions", @@ -5644,6 +5727,8 @@ dependencies = [ "tldextract", "tokio", "toml 0.5.11", + "tower", + "tower-http", "tracing", "tracing-subscriber 0.3.17", "unix_mode", diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index f4c4233f935..a0231c8d037 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -95,6 +95,10 @@ async-trait = "0.1.68" tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] } once_cell = "1.17.1" indicatif = "0.17.5" +axum = "0.6.18" +tower = "0.4.13" +tower-http = "0.4.1" +opener = "0.6.1" # NOTE: Must use different features for clap because the "color" feature does not # work on wasi due to the anstream dependency not compiling. diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 7ed4dfabe34..ae623c274ac 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -9,7 +9,8 @@ use crate::commands::CreateExe; #[cfg(feature = "wast")] use crate::commands::Wast; use crate::commands::{ - Add, Cache, Config, Init, Inspect, Login, Publish, Run, SelfUpdate, Validate, Whoami, + Add, Cache, Config, Init, Inspect, Login, LoginWithBrowser, Publish, Run, SelfUpdate, Validate, + Whoami, }; #[cfg(feature = "static-artifact-create")] use crate::commands::{CreateObj, GenCHeader}; @@ -107,6 +108,7 @@ impl Args { Some(Cmd::Inspect(inspect)) => inspect.execute(), Some(Cmd::Init(init)) => init.execute(), Some(Cmd::Login(login)) => login.execute(), + Some(Cmd::LoginWithBrowser(login_with_browser)) => login_with_browser.execute(), Some(Cmd::Publish(publish)) => publish.execute(), #[cfg(feature = "static-artifact-create")] Some(Cmd::GenCHeader(gen_heder)) => gen_heder.execute(), @@ -137,6 +139,9 @@ enum Cmd { /// Login into a wasmer.io-like registry Login(Login), + #[clap(name = "login-with-browser")] + LoginWithBrowser(LoginWithBrowser), + /// Login into a wasmer.io-like registry #[clap(name = "publish")] Publish(Publish), diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index 9f2365837cd..4f03bb681a1 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -15,6 +15,7 @@ mod gen_c_header; mod init; mod inspect; mod login; +mod login_with_browser; mod publish; mod run; mod self_update; @@ -32,8 +33,8 @@ pub use create_exe::*; #[cfg(feature = "wast")] pub use wast::*; pub use { - add::*, cache::*, config::*, init::*, inspect::*, login::*, publish::*, run::Run, - self_update::*, validate::*, whoami::*, + add::*, cache::*, config::*, init::*, inspect::*, login::*, login_with_browser::*, publish::*, + run::Run, self_update::*, validate::*, whoami::*, }; #[cfg(feature = "static-artifact-create")] pub use {create_obj::*, gen_c_header::*}; diff --git a/lib/cli/src/commands/login_with_browser.rs b/lib/cli/src/commands/login_with_browser.rs new file mode 100644 index 00000000000..dd517e37cad --- /dev/null +++ b/lib/cli/src/commands/login_with_browser.rs @@ -0,0 +1,256 @@ +use std::{net::TcpListener, path::PathBuf, sync::Arc, time::Duration}; + +use anyhow::Ok; + +use clap::Parser; +#[cfg(not(test))] +use dialoguer::Input; +use reqwest::Method; +use tower_http::cors::{Any, CorsLayer}; + +use wasmer_registry::{ + types::NewNonceOutput, + types::ValidatedNonceOutput, + wasmer_env::{Registry, WasmerEnv, WASMER_DIR}, + RegistryClient, +}; + +use axum::{http::StatusCode, routing::post, Json, Router}; + +use tokio::sync::{watch, RwLock}; + +const WASMER_CLI: &str = "wasmer-cli"; + +/// Subcommand for logging in using a browser +#[derive(Debug, Clone, Parser)] +pub struct LoginWithBrowser { + // Note: This is essentially a copy of WasmerEnv except the token is + // accepted as a main argument instead of via --token. + /// Set Wasmer's home directory + #[clap(long, env = "WASMER_DIR", default_value = WASMER_DIR.as_os_str())] + pub wasmer_dir: PathBuf, + /// The registry to fetch packages from (inferred from the environment by + /// default) + #[clap(long, env = "WASMER_REGISTRY")] + pub registry: Option, + /// The API token to use when communicating with the registry (inferred from + /// the environment by default) + pub token: Option, + /// The directory cached artefacts are saved to. + #[clap(long, env = "WASMER_CACHE_DIR")] + cache_dir: Option, +} + +impl LoginWithBrowser { + fn _get_token_or_ask_user(&self, env: &WasmerEnv) -> Result { + if let Some(token) = &self.token { + return Ok(token.clone()); + } + + let registry_host = env.registry_endpoint()?; + let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default()) + .extract(registry_host.as_str()) + .map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid registry for login {}: {e}", registry_host), + ) + })?; + + let login_prompt = match ( + registry_tld.domain.as_deref(), + registry_tld.suffix.as_deref(), + ) { + (Some(d), Some(s)) => { + format!("Please paste the login token from https://{d}.{s}/settings/access-tokens") + } + _ => "Please paste the login token".to_string(), + }; + #[cfg(test)] + { + Ok(login_prompt) + } + #[cfg(not(test))] + { + let token = Input::new().with_prompt(&login_prompt).interact_text()?; + Ok(token) + } + } + + fn wasmer_env(&self) -> WasmerEnv { + WasmerEnv::new( + self.wasmer_dir.clone(), + self.registry.clone(), + self.token.clone(), + self.cache_dir.clone(), + ) + } + + //As this function will only run once so return a Result + async fn save_validated_token(Json(payload): Json) -> StatusCode { + let ValidatedNonceOutput { token } = payload; + println!("Token: {}", token); + StatusCode::OK + } + + async fn setup_listener() -> Result<(TcpListener, String), anyhow::Error> { + let listener = TcpListener::bind("127.0.0.1:0")?; + let addr = listener.local_addr()?; + let port = addr.port(); + + let server_url = format!("http://localhost:{}", port); + + eprintln!("Server URL: {}", server_url); + + Ok((listener, server_url)) + } + + /// execute [List] + #[tokio::main] + pub async fn execute(&self) -> Result<(), anyhow::Error> { + let env = self.wasmer_env(); + // let token = self.get_token_or_ask_user(&env)?; + + let registry = env.registry_endpoint()?; + + let client = RegistryClient::new(registry, None, None); + + // let server_url = Self::setup_server().await?; + let (listener, server_url) = Self::setup_listener().await?; + + let cors_middleware = CorsLayer::new() + .allow_headers([ + axum::http::header::AUTHORIZATION, + axum::http::header::CONTENT_TYPE, + ]) + .allow_methods([Method::POST]) + .allow_origin(Any) + .max_age(Duration::from_secs(60) * 10); + + let app = Router::new().route("/", post(Self::save_validated_token)); + let app = app.layer(cors_middleware); + + let NewNonceOutput { auth_url, .. } = + wasmer_registry::api::get_nonce(&client, WASMER_CLI.to_string(), server_url).await?; + + // currently replace the auth_url with vercel's dev url + // https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli + + let vercel_url="https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli".to_string(); + let auth_url = auth_url.split_once("cli").unwrap().1.to_string(); + let auth_url = vercel_url + &auth_url; + + println!("Opening browser at {}", &auth_url); + opener::open_browser(&auth_url).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to open browser at {} due to {}", &auth_url, e), + ) + })?; + + let (_tx, mut rx) = watch::channel((false, String::new())); + + // let (tx, rx) = watch::channel((false, String::new())); + // let shutdown_signal = Arc::new(RwLock::new(tx)); + + // let app = Router::new().route( + // "/", + // post(move |Json(payload): Json| { + // let ValidatedNonceOutput { token } = payload; + // let mut shutdown_signal = shutdown_signal.write().unwrap(); + // *shutdown_signal = (true, token.clone()); + // StatusCode::OK + // }), + // ); + + axum::Server::from_tcp(listener)? + .serve(app.into_make_service()) + .with_graceful_shutdown(async move { rx.changed().await.unwrap() }) + .await?; + + // match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { + // Some(s) => println!("Login for Wasmer user {:?} saved", s), + // None => println!( + // "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", + // registry, token + // ), + // } + Ok(()) + } +} + +// #[cfg(test)] +// mod tests { +// use clap::CommandFactory; +// use tempfile::TempDir; + +// use super::*; + +// #[test] +// fn interactive_login() { +// let temp = TempDir::new().unwrap(); +// let login = Login { +// registry: Some("wasmer.wtf".into()), +// wasmer_dir: temp.path().to_path_buf(), +// token: None, +// cache_dir: None, +// }; +// let env = login.wasmer_env(); + +// let token = login.get_token_or_ask_user(&env).unwrap(); + +// assert_eq!( +// token, +// "Please paste the login token from https://wasmer.wtf/settings/access-tokens" +// ); +// } + +// #[test] +// fn login_with_token() { +// let temp = TempDir::new().unwrap(); +// let login = Login { +// registry: Some("wasmer.wtf".into()), +// wasmer_dir: temp.path().to_path_buf(), +// token: Some("abc".to_string()), +// cache_dir: None, +// }; +// let env = login.wasmer_env(); + +// let token = login.get_token_or_ask_user(&env).unwrap(); + +// assert_eq!(token, "abc"); +// } + +// #[test] +// fn in_sync_with_wasmer_env() { +// let wasmer_env = WasmerEnv::command(); +// let login = Login::command(); + +// // All options except --token should be the same +// let wasmer_env_opts: Vec<_> = wasmer_env +// .get_opts() +// .filter(|arg| arg.get_id() != "token") +// .collect(); +// let login_opts: Vec<_> = login.get_opts().collect(); + +// assert_eq!(wasmer_env_opts, login_opts); + +// // The token argument should have the same message, even if it is an +// // argument rather than a --flag. +// let wasmer_env_token_help = wasmer_env +// .get_opts() +// .find(|arg| arg.get_id() == "token") +// .unwrap() +// .get_help() +// .unwrap() +// .to_string(); +// let login_token_help = login +// .get_positionals() +// .find(|arg| arg.get_id() == "token") +// .unwrap() +// .get_help() +// .unwrap() +// .to_string(); +// assert_eq!(wasmer_env_token_help, login_token_help); +// } +// } diff --git a/lib/registry/graphql/mutations/new_nonce.graphql b/lib/registry/graphql/mutations/new_nonce.graphql new file mode 100644 index 00000000000..5fb61cc8b2b --- /dev/null +++ b/lib/registry/graphql/mutations/new_nonce.graphql @@ -0,0 +1,9 @@ +mutation NewNonce($name: String!, $callbackUrl: String!) { + newNonce(input: { name: $name, callbackUrl: $callbackUrl }) { + nonce { + id + secret + authUrl + } + } +} diff --git a/lib/registry/graphql/schema.graphql b/lib/registry/graphql/schema.graphql index 0c8392d4ccf..aa506f7de9e 100644 --- a/lib/registry/graphql/schema.graphql +++ b/lib/registry/graphql/schema.graphql @@ -1,59 +1,139 @@ -""" -Exposes a URL that specifies the behaviour of this scalar. -""" -directive @specifiedBy( +interface Node { """ - The URL that specifies the behaviour of this scalar. + The ID of the object """ - url: String! -) on SCALAR - -input AcceptNamespaceCollaboratorInviteInput { - inviteId: ID! - clientMutationId: String -} - -type AcceptNamespaceCollaboratorInvitePayload { - namespaceCollaboratorInvite: NamespaceCollaboratorInvite! - clientMutationId: String -} - -input AcceptPackageCollaboratorInviteInput { - inviteId: ID! - clientMutationId: String + id: ID! } -type AcceptPackageCollaboratorInvitePayload { - packageCollaboratorInvite: PackageCollaboratorInvite! - clientMutationId: String +type PublicKey implements Node { + """ + The ID of the object + """ + id: ID! + owner: User! + keyId: String! + key: String! + revokedAt: DateTime + uploadedAt: DateTime! + verifyingSignature: Signature + revoked: Boolean! } -input AcceptPackageTransferRequestInput { - packageTransferRequestId: ID! - clientMutationId: String -} +type User implements Node & PackageOwner & Owner { + firstName: String! + lastName: String! + email: String! + dateJoined: DateTime! -type AcceptPackageTransferRequestPayload { - package: Package! - packageTransferRequest: PackageTransferRequest! - clientMutationId: String -} + """ + Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. + """ + username: String! + isEmailValidated: Boolean! + bio: String + location: String + websiteUrl: String -type ActivityEvent implements Node { """ The ID of the object """ id: ID! - body: ActivityEventBody! - actorIcon: String! - createdAt: DateTime! + globalName: String! + avatar(size: Int = 80): String! + isViewer: Boolean! + hasUsablePassword: Boolean + fullName: String! + githubUrl: String + twitterUrl: String + publicActivity( + before: String + after: String + first: Int + last: Int + ): ActivityEventConnection! + billing: Billing + waitlist(name: String!): WaitlistMember + namespaces( + role: GrapheneRole + before: String + after: String + first: Int + last: Int + ): NamespaceConnection! + packages( + collaborating: Boolean = false + before: String + after: String + first: Int + last: Int + ): PackageConnection! + apps( + collaborating: Boolean = false + sortBy: DeployAppsSortBy + offset: Int + before: String + after: String + first: Int + last: Int + ): DeployAppConnection! + usageMetrics(forRange: MetricRange!, variant: MetricType!): [UsageMetric]! + isStaff: Boolean + packageVersions( + before: String + after: String + first: Int + last: Int + ): PackageVersionConnection! + packageTransfersIncoming( + before: String + after: String + first: Int + last: Int + ): PackageTransferRequestConnection! + packageInvitesIncoming( + before: String + after: String + first: Int + last: Int + ): PackageCollaboratorInviteConnection! + namespaceInvitesIncoming( + before: String + after: String + first: Int + last: Int + ): NamespaceCollaboratorInviteConnection! + apiTokens( + before: String + after: String + first: Int + last: Int + ): APITokenConnection! + notifications( + before: String + after: String + first: Int + last: Int + ): UserNotificationConnection! } -type ActivityEventBody { - text: String! - ranges: [NodeBodyRange!]! +""" +Setup for backwards compatibility with existing frontends. +""" +interface PackageOwner { + globalName: String! +} + +interface Owner { + globalName: String! } +""" +The `DateTime` scalar type represents a DateTime +value as specified by +[iso8601](https://en.wikipedia.org/wiki/ISO_8601). +""" +scalar DateTime + type ActivityEventConnection { """ Pagination data for this connection. @@ -67,59 +147,38 @@ type ActivityEventConnection { } """ -A Relay edge containing a `ActivityEvent` and its cursor. +The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. """ -type ActivityEventEdge { +type PageInfo { """ - The item at the end of the edge + When paginating forwards, are there more items? """ - node: ActivityEvent + hasNextPage: Boolean! """ - A cursor for use in pagination + When paginating backwards, are there more items? """ - cursor: String! -} - -type AggregateMetrics { - cpuTime: String! - memoryUsage: String! - memoryTime: String! - ingress: String! - egress: String! - mountedVolumes: String! - monthlyCost: String! -} - -type APIToken { - id: ID! - user: User! - identifier: String - createdAt: DateTime! - revokedAt: DateTime - lastUsedAt: DateTime -} + hasPreviousPage: Boolean! -type APITokenConnection { """ - Pagination data for this connection. + When paginating backwards, the cursor to continue. """ - pageInfo: PageInfo! + startCursor: String """ - Contains the nodes in this connection. + When paginating forwards, the cursor to continue. """ - edges: [APITokenEdge]! + endCursor: String } """ -A Relay edge containing a `APIToken` and its cursor. +A Relay edge containing a `ActivityEvent` and its cursor. """ -type APITokenEdge { +type ActivityEventEdge { """ The item at the end of the edge """ - node: APIToken + node: ActivityEvent """ A cursor for use in pagination @@ -127,34 +186,52 @@ type APITokenEdge { cursor: String! } -type AppConnection { +type ActivityEvent implements Node { """ - Pagination data for this connection. + The ID of the object """ - pageInfo: PageInfo! + id: ID! + body: ActivityEventBody! + actorIcon: String! + createdAt: DateTime! +} - """ - Contains the nodes in this connection. - """ - edges: [AppEdge]! +type ActivityEventBody { + text: String! + ranges: [NodeBodyRange!]! } -""" -A Relay edge containing a `App` and its cursor. -""" -type AppEdge { +type NodeBodyRange { + entity: Node! + offset: Int! + length: Int! +} + +type WaitlistMember implements Node { + waitlist: Waitlist! + joinedAt: DateTime! + approvedAt: DateTime + """ - The item at the end of the edge + The ID of the object """ - node: DeployApp + id: ID! + member: Owner! + approved: Boolean! +} + +type Waitlist implements Node { + name: String! + createdAt: DateTime! + updatedAt: DateTime! """ - A cursor for use in pagination + The ID of the object """ - cursor: String! + id: ID! } -type AppVersionConnection { +type NamespaceConnection { """ Pagination data for this connection. """ @@ -163,17 +240,17 @@ type AppVersionConnection { """ Contains the nodes in this connection. """ - edges: [AppVersionEdge]! + edges: [NamespaceEdge]! } """ -A Relay edge containing a `AppVersion` and its cursor. +A Relay edge containing a `Namespace` and its cursor. """ -type AppVersionEdge { +type NamespaceEdge { """ The item at the end of the edge """ - node: DeployAppVersion + node: Namespace """ A cursor for use in pagination @@ -181,48 +258,82 @@ type AppVersionEdge { cursor: String! } -input ArchivePackageInput { - packageId: ID! - clientMutationId: String -} - -type ArchivePackagePayload { - package: Package! - clientMutationId: String -} - -""" -The `BigInt` scalar type represents non-fractional whole numeric values. -`BigInt` is not constrained to 32-bit like the `Int` type and thus is a less -compatible type. -""" -scalar BigInt - -type BindingsGenerator implements Node { +type Namespace implements Node & PackageOwner & Owner { """ The ID of the object """ id: ID! - packageVersion: PackageVersion! - active: Boolean! - commandName: String! - registryJavascriptlanguagebindings( + name: String! + displayName: String + description: String! + avatar: String! + avatarUpdatedAt: DateTime + createdAt: DateTime! + updatedAt: DateTime! + maintainerInvites( offset: Int before: String after: String first: Int last: Int - ): PackageVersionNPMBindingConnection! - registryPythonlanguagebindings( + ): NamespaceCollaboratorInviteConnection! + userSet( offset: Int before: String after: String first: Int last: Int - ): PackageVersionPythonBindingConnection! + ): UserConnection! + globalName: String! + packages( + before: String + after: String + first: Int + last: Int + ): PackageConnection! + apps( + sortBy: DeployAppsSortBy + offset: Int + before: String + after: String + first: Int + last: Int + ): DeployAppConnection! + packageVersions( + before: String + after: String + first: Int + last: Int + ): PackageVersionConnection! + collaborators( + before: String + after: String + first: Int + last: Int + ): NamespaceCollaboratorConnection! + publicActivity( + before: String + after: String + first: Int + last: Int + ): ActivityEventConnection! + pendingInvites( + before: String + after: String + first: Int + last: Int + ): NamespaceCollaboratorInviteConnection! + viewerHasRole(role: GrapheneRole!): Boolean! + packageTransfersIncoming( + before: String + after: String + first: Int + last: Int + ): PackageTransferRequestConnection! + usageMetrics(forRange: MetricRange!, variant: MetricType!): [UsageMetric]! } -type BindingsGeneratorConnection { +type NamespaceCollaboratorInviteConnection { """ Pagination data for this connection. """ @@ -231,17 +342,17 @@ type BindingsGeneratorConnection { """ Contains the nodes in this connection. """ - edges: [BindingsGeneratorEdge]! + edges: [NamespaceCollaboratorInviteEdge]! } """ -A Relay edge containing a `BindingsGenerator` and its cursor. +A Relay edge containing a `NamespaceCollaboratorInvite` and its cursor. """ -type BindingsGeneratorEdge { +type NamespaceCollaboratorInviteEdge { """ The item at the end of the edge """ - node: BindingsGenerator + node: NamespaceCollaboratorInvite """ A cursor for use in pagination @@ -249,89 +360,72 @@ type BindingsGeneratorEdge { cursor: String! } -input ChangePackageVersionArchivedStatusInput { - packageVersionId: ID! - isArchived: Boolean - clientMutationId: String -} - -type ChangePackageVersionArchivedStatusPayload { - packageVersion: PackageVersion! - clientMutationId: String -} - -input ChangeUserEmailInput { - newEmail: String! - clientMutationId: String -} - -type ChangeUserEmailPayload { - user: User! - clientMutationId: String -} - -input ChangeUserPasswordInput { +type NamespaceCollaboratorInvite implements Node { """ - The token associated to change the password. If not existing it will use the request user by default + The ID of the object """ - token: String - oldPassword: String - password: String! - clientMutationId: String + id: ID! + requestedBy: User! + user: User + inviteEmail: String + namespace: Namespace! + role: RegistryNamespaceMaintainerInviteRoleChoices! + accepted: NamespaceCollaborator + approvedBy: User + declinedBy: User + createdAt: DateTime! + expiresAt: DateTime! + closedAt: DateTime } -type ChangeUserPasswordPayload { - token: String - clientMutationId: String -} +enum RegistryNamespaceMaintainerInviteRoleChoices { + """ + Admin + """ + ADMIN -input ChangeUserUsernameInput { """ - The new user username + Editor """ - username: String! - clientMutationId: String -} + EDITOR -type ChangeUserUsernamePayload { - user: User - token: String - clientMutationId: String + """ + Viewer + """ + VIEWER } -input CheckUserExistsInput { +type NamespaceCollaborator implements Node { """ - The user + The ID of the object """ - user: String! - clientMutationId: String + id: ID! + user: User! + role: RegistryNamespaceMaintainerRoleChoices! + namespace: Namespace! + createdAt: DateTime! + updatedAt: DateTime! + invite: NamespaceCollaboratorInvite } -type CheckUserExistsPayload { - exists: Boolean! +enum RegistryNamespaceMaintainerRoleChoices { + """ + Admin + """ + ADMIN """ - The user is only returned if the user input was the username + Editor """ - user: User - clientMutationId: String -} + EDITOR -type Collection { - slug: String! - displayName: String! - description: String! - createdAt: DateTime! - banner: String! - packages( - before: String - after: String - first: Int - last: Int - ): PackageConnection! + """ + Viewer + """ + VIEWER } -type CollectionConnection { +type UserConnection { """ Pagination data for this connection. """ @@ -340,17 +434,22 @@ type CollectionConnection { """ Contains the nodes in this connection. """ - edges: [CollectionEdge]! + edges: [UserEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int } """ -A Relay edge containing a `Collection` and its cursor. +A Relay edge containing a `User` and its cursor. """ -type CollectionEdge { +type UserEdge { """ The item at the end of the edge """ - node: Collection + node: User """ A cursor for use in pagination @@ -358,211 +457,576 @@ type CollectionEdge { cursor: String! } -type Command { - command: String! - packageVersion: PackageVersion! - module: PackageVersionModule! -} - -input CreateNamespaceInput { - name: String! - - """ - The namespace display name - """ - displayName: String - +type PackageConnection { """ - The namespace description + Pagination data for this connection. """ - description: String + pageInfo: PageInfo! """ - The namespace avatar + Contains the nodes in this connection. """ - avatar: String - clientMutationId: String -} - -type CreateNamespacePayload { - namespace: Namespace! - user: User! - clientMutationId: String + edges: [PackageEdge]! } """ -The `DateTime` scalar type represents a DateTime -value as specified by -[iso8601](https://en.wikipedia.org/wiki/ISO_8601). +A Relay edge containing a `Package` and its cursor. """ -scalar DateTime - -input DeleteNamespaceInput { - namespaceId: ID! - clientMutationId: String -} +type PackageEdge { + """ + The item at the end of the edge + """ + node: Package -type DeleteNamespacePayload { - success: Boolean! - clientMutationId: String + """ + A cursor for use in pagination + """ + cursor: String! } -type DeployApp implements Node & Owner { +type Package implements Likeable & Node & PackageOwner { """ The ID of the object """ id: ID! name: String! - createdBy: User! + namespace: String + private: Boolean! createdAt: DateTime! - activeVersion: DeployAppVersion! - globalName: String! - url: String! - description: String - owner: Owner! - versions( - before: String + updatedAt: DateTime! + maintainers: [User]! @deprecated(reason: "Please use collaborators instead") + curated: Boolean! + ownerObjectId: Int! + lastVersion: PackageVersion + + """ + The app icon. It should be formatted in the same way as Apple icons + """ + icon: String! + totalDownloads: Int! + iconUpdatedAt: DateTime + watchersCount: Int! + versions: [PackageVersion]! + collectionSet: [Collection!]! + likersCount: Int! + viewerHasLiked: Boolean! + globalName: String! + alias: String + displayName: String! + + """ + The name of the package without the owner + """ + packageName: String! + + """ + The app icon. It should be formatted in the same way as Apple icons + """ + appIcon: String! @deprecated(reason: "Please use icon instead") + + """ + The total number of downloads of the package + """ + downloadsCount: Int + + """ + The public keys for all the published versions + """ + publicKeys: [PublicKey!]! + collaborators( + before: String + after: String + first: Int + last: Int + ): PackageCollaboratorConnection! + pendingInvites( + before: String + after: String + first: Int + last: Int + ): PackageCollaboratorInviteConnection! + viewerHasRole(role: GrapheneRole!): Boolean! + owner: PackageOwner! + isTransferring: Boolean! + activeTransferRequest: PackageTransferRequest + isArchived: Boolean! + viewerIsWatching: Boolean! +} + +interface Likeable { + id: ID! + likersCount: Int! + viewerHasLiked: Boolean! +} + +type PackageVersion implements Node { + """ + The ID of the object + """ + id: ID! + package: Package! + version: String! + description: String! + manifest: String! + license: String + licenseFile: String + readme: String + witMd: String + repository: String + homepage: String + createdAt: DateTime! + updatedAt: DateTime! + staticObjectsCompiled: Boolean! + nativeExecutablesCompiled: Boolean! + publishedBy: User! + signature: Signature + isArchived: Boolean! + file: String! + + """ + """ + fileSize: BigInt! + piritaFile: String + + """ + """ + piritaFileSize: BigInt! + piritaManifest: JSONString + piritaVolumes: JSONString + totalDownloads: Int! + pirita256hash: String + @deprecated(reason: "Please use distribution.piritaSha256Hash instead.") + bindingsState: RegistryPackageVersionBindingsStateChoices! + nativeExecutablesState: RegistryPackageVersionNativeExecutablesStateChoices! + lastversionPackage( + offset: Int + before: String + after: String + first: Int + last: Int + ): PackageConnection! + commands: [Command!]! + nativeexecutableSet( + offset: Int + before: String + after: String + first: Int + last: Int + ): NativeExecutableConnection! + bindingsgeneratorSet( + offset: Int + before: String + after: String + first: Int + last: Int + ): BindingsGeneratorConnection! + javascriptlanguagebindingSet( + offset: Int + before: String + after: String + first: Int + last: Int + ): PackageVersionNPMBindingConnection! + pythonlanguagebindingSet( + offset: Int + before: String + after: String + first: Int + last: Int + ): PackageVersionPythonBindingConnection! + distribution: PackageDistribution! + filesystem: [PackageVersionFilesystem]! + isLastVersion: Boolean! + witFile: String + isSigned: Boolean! + moduleInterfaces: [InterfaceVersion!]! + modules: [PackageVersionModule!]! + getPiritaContents( + volume: String! = "atom" + root: String! = "" + ): [PiritaFilesystemItem!]! + nativeExecutables( + triple: String + wasmerCompilerVersion: String + ): [NativeExecutable] + bindings: [PackageVersionLanguageBinding]! + npmBindings: PackageVersionNPMBinding + pythonBindings: PackageVersionPythonBinding + hasBindings: Boolean! + hasCommands: Boolean! +} + +""" +The `BigInt` scalar type represents non-fractional whole numeric values. +`BigInt` is not constrained to 32-bit like the `Int` type and thus is a less +compatible type. +""" +scalar BigInt + +""" +Allows use of a JSON String for input / output from the GraphQL schema. + +Use of this type is *not recommended* as you lose the benefits of having a defined, static +schema (one of the key benefits of GraphQL). +""" +scalar JSONString + +enum RegistryPackageVersionBindingsStateChoices { + """ + Bindings are not detected + """ + NOT_PRESENT + + """ + Bindings are being built + """ + GENERATING + + """ + Bindings generation has failed + """ + ERROR + + """ + Bindings are built and present + """ + GENERATED_AND_PRESENT +} + +enum RegistryPackageVersionNativeExecutablesStateChoices { + """ + Native Executables are not detected + """ + NOT_PRESENT + + """ + Native Executables are being built + """ + GENERATING + + """ + Native Executables generation has failed + """ + ERROR + + """ + Native Executables are built and present + """ + GENERATED_AND_PRESENT +} + +type Command { + command: String! + packageVersion: PackageVersion! + module: PackageVersionModule! +} + +type PackageVersionModule { + name: String! + source: String! + abi: String + publicUrl: String! +} + +type NativeExecutableConnection { + """ + Pagination data for this connection. + """ + pageInfo: PageInfo! + + """ + Contains the nodes in this connection. + """ + edges: [NativeExecutableEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int +} + +""" +A Relay edge containing a `NativeExecutable` and its cursor. +""" +type NativeExecutableEdge { + """ + The item at the end of the edge + """ + node: NativeExecutable + + """ + A cursor for use in pagination + """ + cursor: String! +} + +type NativeExecutable implements Node { + """ + The ID of the object + """ + id: ID! + module: String! @deprecated(reason: "Use filename instead") + filename: String! + targetTriple: String! + downloadUrl: String! +} + +type BindingsGeneratorConnection { + """ + Pagination data for this connection. + """ + pageInfo: PageInfo! + + """ + Contains the nodes in this connection. + """ + edges: [BindingsGeneratorEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int +} + +""" +A Relay edge containing a `BindingsGenerator` and its cursor. +""" +type BindingsGeneratorEdge { + """ + The item at the end of the edge + """ + node: BindingsGenerator + + """ + A cursor for use in pagination + """ + cursor: String! +} + +type BindingsGenerator implements Node { + """ + The ID of the object + """ + id: ID! + packageVersion: PackageVersion! + active: Boolean! + commandName: String! + registryJavascriptlanguagebindings( + offset: Int + before: String after: String first: Int last: Int - ): AppVersionConnection! - aggregateMetrics: AggregateMetrics! + ): PackageVersionNPMBindingConnection! + registryPythonlanguagebindings( + offset: Int + before: String + after: String + first: Int + last: Int + ): PackageVersionPythonBindingConnection! } -type DeployAppVersion implements Node { +type PackageVersionNPMBindingConnection { """ - The ID of the object + Pagination data for this connection. """ - id: ID! - app: DeployApp - configWebc: String - signature: String - description: String - publishedBy: User! - createdAt: DateTime! - webcUrl: String! - config: String! - url: String! - version: String! - manifest: String! + pageInfo: PageInfo! + + """ + Contains the nodes in this connection. + """ + edges: [PackageVersionNPMBindingEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int } -type DeployConfigInfo implements Node { +""" +A Relay edge containing a `PackageVersionNPMBinding` and its cursor. +""" +type PackageVersionNPMBindingEdge { """ - The ID of the object + The item at the end of the edge """ - id: ID! - name: String! - createdBy: User! - createdAt: DateTime! - updatedAt: DateTime! - versions: [DeployConfigVersion] - publishedBy: User - namespace: String! + node: PackageVersionNPMBinding + + """ + A cursor for use in pagination + """ + cursor: String! } -type DeployConfigVersion implements Node { +type PackageVersionNPMBinding implements PackageVersionLanguageBinding & Node { """ The ID of the object """ id: ID! - configWebc: String - signature: String - description: String + language: ProgrammingLanguage! + + """ + The URL of the generated artifacts on Wasmer CDN. + """ + url: String! + + """ + When the binding was generated + """ createdAt: DateTime! - versionNumber: BigInt! - fileOffset: Int - fileSize: Int - updatedAt: DateTime! - webcUrl: String! - config: String! - info: DeployConfigInfo -} -type ErrorType { - field: String! - messages: [String!]! -} + """ + Package version used to generate this binding + """ + generator: BindingsGenerator! + name: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + kind: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) -input GenerateAPITokenInput { - identifier: String - clientMutationId: String + """ + Name of package source + """ + packageName: String! + module: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + npmDefaultInstallPackageName(url: String): String! + @deprecated(reason: "Please use packageName instead") } -type GenerateAPITokenPayload { - token: APIToken - tokenRaw: String - user: User - clientMutationId: String -} +interface PackageVersionLanguageBinding { + id: ID! + language: ProgrammingLanguage! -input GenerateBindingsForAllPackagesInput { - bindingsGeneratorId: ID - bindingsGeneratorCommand: String - clientMutationId: String -} + """ + The URL of the generated artifacts on Wasmer CDN. + """ + url: String! -type GenerateBindingsForAllPackagesPayload { - message: String! - clientMutationId: String + """ + When the binding was generated + """ + createdAt: DateTime! + + """ + Package version used to generate this binding + """ + generator: BindingsGenerator! + name: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + kind: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + + """ + Name of package source + """ + packageName: String! + module: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) } -input GenerateDeployTokenInput { - deployConfigVersionId: String! - clientMutationId: String +enum ProgrammingLanguage { + PYTHON + JAVASCRIPT } -type GenerateDeployTokenPayload { - token: String! - deployConfigVersion: DeployConfigVersion! - clientMutationId: String +type PackageVersionPythonBindingConnection { + """ + Pagination data for this connection. + """ + pageInfo: PageInfo! + + """ + Contains the nodes in this connection. + """ + edges: [PackageVersionPythonBindingEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int } """ -The `GenericScalar` scalar type represents a generic -GraphQL scalar value that could be: -String, Boolean, Int, Float, List or Object. +A Relay edge containing a `PackageVersionPythonBinding` and its cursor. """ -scalar GenericScalar +type PackageVersionPythonBindingEdge { + """ + The item at the end of the edge + """ + node: PackageVersionPythonBinding -type GetPasswordResetToken { - valid: Boolean! - user: User + """ + A cursor for use in pagination + """ + cursor: String! } -union GlobalObject = User | Namespace +type PackageVersionPythonBinding implements PackageVersionLanguageBinding & Node { + """ + The ID of the object + """ + id: ID! + language: ProgrammingLanguage! -enum GrapheneRole { - ADMIN - EDITOR - VIEWER -} + """ + The URL of the generated artifacts on Wasmer CDN. + """ + url: String! -input InputSignature { - publicKeyKeyId: String! - data: String! -} + """ + When the binding was generated + """ + createdAt: DateTime! -type Interface implements Node { """ - The ID of the object + Package version used to generate this binding """ - id: ID! + generator: BindingsGenerator! name: String! - displayName: String! - description: String! - homepage: String - icon: String - createdAt: DateTime! - updatedAt: DateTime! - versions( - offset: Int - before: String - after: String - first: Int - last: Int - ): InterfaceVersionConnection! - lastVersion: InterfaceVersion + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + kind: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + + """ + Name of package source + """ + packageName: String! + module: String! + @deprecated( + reason: "Do not use this field, since bindings for all modules are generated at once now." + ) + pythonDefaultInstallPackageName(url: String): String! +} + +type PackageDistribution { + downloadUrl: String! + size: Int! + piritaDownloadUrl: String + piritaSize: Int! + piritaSha256Hash: String +} + +type PackageVersionFilesystem { + wasm: String! + host: String! } type InterfaceVersion implements Node { @@ -585,266 +1049,29 @@ type InterfaceVersion implements Node { ): PackageVersionConnection! } -type InterfaceVersionConnection { - """ - Pagination data for this connection. - """ - pageInfo: PageInfo! - - """ - Contains the nodes in this connection. - """ - edges: [InterfaceVersionEdge]! -} - -""" -A Relay edge containing a `InterfaceVersion` and its cursor. -""" -type InterfaceVersionEdge { - """ - The item at the end of the edge - """ - node: InterfaceVersion - - """ - A cursor for use in pagination - """ - cursor: String! -} - -input InviteNamespaceCollaboratorInput { - namespaceId: ID! - role: GrapheneRole! - username: String - email: String - clientMutationId: String -} - -type InviteNamespaceCollaboratorPayload { - invite: NamespaceCollaboratorInvite! - namespace: Namespace! - clientMutationId: String -} - -input InvitePackageCollaboratorInput { - packageName: String! - role: GrapheneRole! - username: String - email: String - clientMutationId: String -} - -type InvitePackageCollaboratorPayload { - invite: PackageCollaboratorInvite! - package: Package! - clientMutationId: String -} - -""" -Allows use of a JSON String for input / output from the GraphQL schema. - -Use of this type is *not recommended* as you lose the benefits of having a defined, static -schema (one of the key benefits of GraphQL). -""" -scalar JSONString - -interface Likeable { - id: ID! - likersCount: Int! - viewerHasLiked: Boolean! -} - -input LikePackageInput { - packageId: ID! - clientMutationId: String -} - -type LikePackagePayload { - package: Package! - clientMutationId: String -} - -type Mutation { - publishDeployConfig( - input: PublishDeployConfigInput! - ): PublishDeployConfigPayload - @deprecated(reason: "Please use publishDeployApp instead.") - publishDeployApp(input: PublishDeployAppInput!): PublishDeployAppPayload - tokenAuth(input: ObtainJSONWebTokenInput!): ObtainJSONWebTokenPayload - generateDeployToken( - input: GenerateDeployTokenInput! - ): GenerateDeployTokenPayload - verifyAccessToken(token: String): Verify - refreshAccessToken(refreshToken: String): Refresh - revokeAccessToken(refreshToken: String): Revoke - registerUser(input: RegisterUserInput!): RegisterUserPayload - socialAuth(input: SocialAuthJWTInput!): SocialAuthJWTPayload - validateUserEmail(input: ValidateUserEmailInput!): ValidateUserEmailPayload - requestPasswordReset( - input: RequestPasswordResetInput! - ): RequestPasswordResetPayload - requestValidationEmail( - input: RequestValidationEmailInput! - ): RequestValidationEmailPayload - changeUserPassword(input: ChangeUserPasswordInput!): ChangeUserPasswordPayload - changeUserUsername(input: ChangeUserUsernameInput!): ChangeUserUsernamePayload - changeUserEmail(input: ChangeUserEmailInput!): ChangeUserEmailPayload - updateUserInfo(input: UpdateUserInfoInput!): UpdateUserInfoPayload - validateUserPassword( - input: ValidateUserPasswordInput! - ): ValidateUserPasswordPayload - generateApiToken(input: GenerateAPITokenInput!): GenerateAPITokenPayload - revokeApiToken(input: RevokeAPITokenInput!): RevokeAPITokenPayload - checkUserExists(input: CheckUserExistsInput!): CheckUserExistsPayload - readNotification(input: ReadNotificationInput!): ReadNotificationPayload - seePendingNotifications( - input: SeePendingNotificationsInput! - ): SeePendingNotificationsPayload - publishPublicKey(input: PublishPublicKeyInput!): PublishPublicKeyPayload - publishPackage(input: PublishPackageInput!): PublishPackagePayload - updatePackage(input: UpdatePackageInput!): UpdatePackagePayload - likePackage(input: LikePackageInput!): LikePackagePayload - unlikePackage(input: UnlikePackageInput!): UnlikePackagePayload - watchPackage(input: WatchPackageInput!): WatchPackagePayload - unwatchPackage(input: UnwatchPackageInput!): UnwatchPackagePayload - archivePackage(input: ArchivePackageInput!): ArchivePackagePayload - changePackageVersionArchivedStatus( - input: ChangePackageVersionArchivedStatusInput! - ): ChangePackageVersionArchivedStatusPayload - createNamespace(input: CreateNamespaceInput!): CreateNamespacePayload - updateNamespace(input: UpdateNamespaceInput!): UpdateNamespacePayload - deleteNamespace(input: DeleteNamespaceInput!): DeleteNamespacePayload - inviteNamespaceCollaborator( - input: InviteNamespaceCollaboratorInput! - ): InviteNamespaceCollaboratorPayload - acceptNamespaceCollaboratorInvite( - input: AcceptNamespaceCollaboratorInviteInput! - ): AcceptNamespaceCollaboratorInvitePayload - removeNamespaceCollaboratorInvite( - input: RemoveNamespaceCollaboratorInviteInput! - ): RemoveNamespaceCollaboratorInvitePayload - removeNamespaceCollaborator( - input: RemoveNamespaceCollaboratorInput! - ): RemoveNamespaceCollaboratorPayload - updateNamespaceCollaboratorRole( - input: UpdateNamespaceCollaboratorRoleInput! - ): UpdateNamespaceCollaboratorRolePayload - updateNamespaceCollaboratorInviteRole( - input: UpdateNamespaceCollaboratorInviteRoleInput! - ): UpdateNamespaceCollaboratorInviteRolePayload - invitePackageCollaborator( - input: InvitePackageCollaboratorInput! - ): InvitePackageCollaboratorPayload - acceptPackageCollaboratorInvite( - input: AcceptPackageCollaboratorInviteInput! - ): AcceptPackageCollaboratorInvitePayload - removePackageCollaboratorInvite( - input: RemovePackageCollaboratorInviteInput! - ): RemovePackageCollaboratorInvitePayload - updatePackageCollaboratorRole( - input: UpdatePackageCollaboratorRoleInput! - ): UpdatePackageCollaboratorRolePayload - updatePackageCollaboratorInviteRole( - input: UpdatePackageCollaboratorInviteRoleInput! - ): UpdatePackageCollaboratorInviteRolePayload - removePackageCollaborator( - input: RemovePackageCollaboratorInput! - ): RemovePackageCollaboratorPayload - requestPackageTransfer( - input: RequestPackageTransferInput! - ): RequestPackageTransferPayload - acceptPackageTransferRequest( - input: AcceptPackageTransferRequestInput! - ): AcceptPackageTransferRequestPayload - removePackageTransferRequest( - input: RemovePackageTransferRequestInput! - ): RemovePackageTransferRequestPayload - generateBindingsForAllPackages( - input: GenerateBindingsForAllPackagesInput! - ): GenerateBindingsForAllPackagesPayload -} - -type Namespace implements Node & PackageOwner & Owner { +type Interface implements Node { """ The ID of the object """ id: ID! name: String! - displayName: String + displayName: String! description: String! - avatar: String! - avatarUpdatedAt: DateTime + homepage: String + icon: String createdAt: DateTime! updatedAt: DateTime! - maintainerInvites( - offset: Int - before: String - after: String - first: Int - last: Int - ): NamespaceCollaboratorInviteConnection! - userSet( + versions( offset: Int before: String after: String first: Int last: Int - ): UserConnection! - globalName: String! - packages( - before: String - after: String - first: Int - last: Int - ): PackageConnection! - apps(before: String, after: String, first: Int, last: Int): AppConnection! - packageVersions( - before: String - after: String - first: Int - last: Int - ): PackageVersionConnection! - collaborators( - before: String - after: String - first: Int - last: Int - ): NamespaceCollaboratorConnection! - publicActivity( - before: String - after: String - first: Int - last: Int - ): ActivityEventConnection! - pendingInvites( - before: String - after: String - first: Int - last: Int - ): NamespaceCollaboratorInviteConnection! - viewerHasRole(role: GrapheneRole!): Boolean! - packageTransfersIncoming( - before: String - after: String - first: Int - last: Int - ): PackageTransferRequestConnection! -} - -type NamespaceCollaborator implements Node { - """ - The ID of the object - """ - id: ID! - user: User! - role: RegistryNamespaceMaintainerRoleChoices! - namespace: Namespace! - createdAt: DateTime! - updatedAt: DateTime! - invite: NamespaceCollaboratorInvite + ): InterfaceVersionConnection! + lastVersion: InterfaceVersion } -type NamespaceCollaboratorConnection { +type InterfaceVersionConnection { """ Pagination data for this connection. """ @@ -853,17 +1080,22 @@ type NamespaceCollaboratorConnection { """ Contains the nodes in this connection. """ - edges: [NamespaceCollaboratorEdge]! + edges: [InterfaceVersionEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int } """ -A Relay edge containing a `NamespaceCollaborator` and its cursor. +A Relay edge containing a `InterfaceVersion` and its cursor. """ -type NamespaceCollaboratorEdge { +type InterfaceVersionEdge { """ The item at the end of the edge """ - node: NamespaceCollaborator + node: InterfaceVersion """ A cursor for use in pagination @@ -871,25 +1103,7 @@ type NamespaceCollaboratorEdge { cursor: String! } -type NamespaceCollaboratorInvite implements Node { - """ - The ID of the object - """ - id: ID! - requestedBy: User! - user: User - inviteEmail: String - namespace: Namespace! - role: RegistryNamespaceMaintainerInviteRoleChoices! - accepted: NamespaceCollaborator - approvedBy: User - declinedBy: User - createdAt: DateTime! - expiresAt: DateTime! - closedAt: DateTime -} - -type NamespaceCollaboratorInviteConnection { +type PackageVersionConnection { """ Pagination data for this connection. """ @@ -898,17 +1112,17 @@ type NamespaceCollaboratorInviteConnection { """ Contains the nodes in this connection. """ - edges: [NamespaceCollaboratorInviteEdge]! + edges: [PackageVersionEdge]! } """ -A Relay edge containing a `NamespaceCollaboratorInvite` and its cursor. +A Relay edge containing a `PackageVersion` and its cursor. """ -type NamespaceCollaboratorInviteEdge { +type PackageVersionEdge { """ The item at the end of the edge """ - node: NamespaceCollaboratorInvite + node: PackageVersion """ A cursor for use in pagination @@ -916,7 +1130,38 @@ type NamespaceCollaboratorInviteEdge { cursor: String! } -type NamespaceConnection { +union PiritaFilesystemItem = PiritaFilesystemFile | PiritaFilesystemDir + +type PiritaFilesystemFile { + name(display: PiritaFilesystemNameDisplay): String! + size: Int! + offset: Int! +} + +enum PiritaFilesystemNameDisplay { + RELATIVE + ABSOLUTE +} + +type PiritaFilesystemDir { + name(display: PiritaFilesystemNameDisplay): String! +} + +type Collection { + slug: String! + displayName: String! + description: String! + createdAt: DateTime! + banner: String! + packages( + before: String + after: String + first: Int + last: Int + ): PackageConnection! +} + +type PackageCollaboratorConnection { """ Pagination data for this connection. """ @@ -925,17 +1170,17 @@ type NamespaceConnection { """ Contains the nodes in this connection. """ - edges: [NamespaceEdge]! + edges: [PackageCollaboratorEdge]! } """ -A Relay edge containing a `Namespace` and its cursor. +A Relay edge containing a `PackageCollaborator` and its cursor. """ -type NamespaceEdge { +type PackageCollaboratorEdge { """ The item at the end of the edge """ - node: Namespace + node: PackageCollaborator """ A cursor for use in pagination @@ -943,18 +1188,72 @@ type NamespaceEdge { cursor: String! } -type NativeExecutable implements Node { +type PackageCollaborator implements Node { """ The ID of the object """ id: ID! - module: String! @deprecated(reason: "Use filename instead") - filename: String! - targetTriple: String! - downloadUrl: String! + user: User! + role: RegistryPackageMaintainerRoleChoices! + package: Package! + createdAt: DateTime! + updatedAt: DateTime! + invite: PackageCollaboratorInvite } -type NativeExecutableConnection { +enum RegistryPackageMaintainerRoleChoices { + """ + Admin + """ + ADMIN + + """ + Editor + """ + EDITOR + + """ + Viewer + """ + VIEWER +} + +type PackageCollaboratorInvite implements Node { + """ + The ID of the object + """ + id: ID! + requestedBy: User! + user: User + inviteEmail: String + package: Package! + role: RegistryPackageMaintainerInviteRoleChoices! + accepted: PackageCollaborator + approvedBy: User + declinedBy: User + createdAt: DateTime! + expiresAt: DateTime! + closedAt: DateTime +} + +enum RegistryPackageMaintainerInviteRoleChoices { + """ + Admin + """ + ADMIN + + """ + Editor + """ + EDITOR + + """ + Viewer + """ + VIEWER +} + +type PackageCollaboratorInviteConnection { """ Pagination data for this connection. """ @@ -963,17 +1262,17 @@ type NativeExecutableConnection { """ Contains the nodes in this connection. """ - edges: [NativeExecutableEdge]! + edges: [PackageCollaboratorInviteEdge]! } """ -A Relay edge containing a `NativeExecutable` and its cursor. +A Relay edge containing a `PackageCollaboratorInvite` and its cursor. """ -type NativeExecutableEdge { +type PackageCollaboratorInviteEdge { """ The item at the end of the edge """ - node: NativeExecutable + node: PackageCollaboratorInvite """ A cursor for use in pagination @@ -981,121 +1280,142 @@ type NativeExecutableEdge { cursor: String! } -interface Node { +enum GrapheneRole { + ADMIN + EDITOR + VIEWER +} + +type PackageTransferRequest implements Node { """ The ID of the object """ id: ID! + requestedBy: User! + previousOwnerObjectId: Int! + newOwnerObjectId: Int! + package: Package! + approvedBy: User + declinedBy: User + createdAt: DateTime! + expiresAt: DateTime! + closedAt: DateTime + previousOwner: PackageOwner! + newOwner: PackageOwner! } -type NodeBodyRange { - entity: Node! - offset: Int! - length: Int! -} - -input ObtainJSONWebTokenInput { - clientMutationId: String - username: String! - password: String! -} - -type ObtainJSONWebTokenPayload { - payload: GenericScalar! - refreshExpiresIn: Int! - username: String! - clientMutationId: String - token: String! - refreshToken: String! -} - -interface Owner { - globalName: String! -} - -type Package implements Likeable & Node & PackageOwner { +type DeployAppConnection { """ - The ID of the object + Pagination data for this connection. """ - id: ID! - name: String! - namespace: String - private: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - maintainers: [User]! @deprecated(reason: "Please use collaborators instead") - curated: Boolean! - ownerObjectId: Int! - lastVersion: PackageVersion + pageInfo: PageInfo! """ - The app icon. It should be formatted in the same way as Apple icons + Contains the nodes in this connection. """ - icon: String! - totalDownloads: Int! - iconUpdatedAt: DateTime - watchersCount: Int! - versions: [PackageVersion]! - collectionSet: [Collection!]! - likersCount: Int! - viewerHasLiked: Boolean! - globalName: String! - alias: String - displayName: String! + edges: [DeployAppEdge]! """ - The name of the package without the owner + Total number of items in the connection. """ - packageName: String! + totalCount: Int +} +""" +A Relay edge containing a `DeployApp` and its cursor. +""" +type DeployAppEdge { """ - The app icon. It should be formatted in the same way as Apple icons + The item at the end of the edge """ - appIcon: String! @deprecated(reason: "Please use icon instead") + node: DeployApp """ - The total number of downloads of the package + A cursor for use in pagination """ - downloadsCount: Int + cursor: String! +} +type DeployApp implements Node & Owner { """ - The public keys for all the published versions + The ID of the object """ - publicKeys: [PublicKey!]! - collaborators( + id: ID! + createdBy: User! + createdAt: DateTime! + updatedAt: DateTime! + activeVersion: DeployAppVersion! + globalName: String! + url: String! + adminUrl: String! + permalink: String! + urls: [String]! + description: String + name: String! + owner: Owner! + versions( + sortBy: DeployAppVersionsSortBy + createdAfter: DateTime + offset: Int before: String after: String first: Int last: Int - ): PackageCollaboratorConnection! - pendingInvites( + ): DeployAppVersionConnection! + aggregateMetrics: AggregateMetrics! + aliases( + offset: Int before: String after: String first: Int last: Int - ): PackageCollaboratorInviteConnection! - viewerHasRole(role: GrapheneRole!): Boolean! - owner: PackageOwner! - isTransferring: Boolean! - activeTransferRequest: PackageTransferRequest - isArchived: Boolean! - viewerIsWatching: Boolean! + ): AppAliasConnection! + usageMetrics(forRange: MetricRange!, variant: MetricType!): [UsageMetric]! } -type PackageCollaborator implements Node { +type DeployAppVersion implements Node { """ The ID of the object """ id: ID! - user: User! - role: RegistryPackageMaintainerRoleChoices! - package: Package! + app: DeployApp! + yamlConfig: String! + userYamlConfig: String! + signature: String + description: String + publishedBy: User! createdAt: DateTime! - updatedAt: DateTime! - invite: PackageCollaboratorInvite + configWebc: String + @deprecated(reason: "webc support has been deprecated for apps") + config: String! @deprecated(reason: "Please use jsonConfig instead") + jsonConfig: String! + url: String! + permalink: String! + urls: [String]! + version: String! + isActive: Boolean! + manifest: String! + logs( + """ + Get logs starting from this timestamp. Takes EPOCH timestamp in seconds. + """ + startingFrom: Float! + + """ + Fetch logs until this timestamp. Takes EPOCH timestamp in seconds. + """ + until: Float + before: String + after: String + first: Int + last: Int + ): LogConnection! + usageMetrics(forRange: MetricRange!, variant: MetricType!): [UsageMetric]! + sourcePackageVersion: PackageVersion! + aggregateMetrics: AggregateMetrics! } -type PackageCollaboratorConnection { +type LogConnection { """ Pagination data for this connection. """ @@ -1104,17 +1424,17 @@ type PackageCollaboratorConnection { """ Contains the nodes in this connection. """ - edges: [PackageCollaboratorEdge]! + edges: [LogEdge]! } """ -A Relay edge containing a `PackageCollaborator` and its cursor. +A Relay edge containing a `Log` and its cursor. """ -type PackageCollaboratorEdge { +type LogEdge { """ The item at the end of the edge """ - node: PackageCollaborator + node: Log """ A cursor for use in pagination @@ -1122,52 +1442,67 @@ type PackageCollaboratorEdge { cursor: String! } -type PackageCollaboratorInvite implements Node { +type UsageMetric { + variant: MetricType! + value: Float! + unit: MetricUnit! + timestamp: DateTime! +} + +enum MetricType { + cpu_time + memory_time + network_egress + network_ingress + no_of_requests + cost +} + +""" +Units for metrics +""" +enum MetricUnit { """ - The ID of the object + represents the unit of "seconds". """ - id: ID! - requestedBy: User! - user: User - inviteEmail: String - package: Package! - role: RegistryPackageMaintainerInviteRoleChoices! - accepted: PackageCollaborator - approvedBy: User - declinedBy: User - createdAt: DateTime! - expiresAt: DateTime! - closedAt: DateTime -} + SEC -type PackageCollaboratorInviteConnection { """ - Pagination data for this connection. + represents the unit of "kilobytes". """ - pageInfo: PageInfo! + KB """ - Contains the nodes in this connection. + represents the unit of "kilobytes per second". """ - edges: [PackageCollaboratorInviteEdge]! -} + KBS -""" -A Relay edge containing a `PackageCollaboratorInvite` and its cursor. -""" -type PackageCollaboratorInviteEdge { """ - The item at the end of the edge + represents the unit of "number of requests". """ - node: PackageCollaboratorInvite + NO_REQUESTS """ - A cursor for use in pagination + represents the unit of "cost" in USD. """ - cursor: String! + DOLLARS } -type PackageConnection { +enum MetricRange { + LAST_24_HOURS + LAST_30_DAYS +} + +type AggregateMetrics { + cpuTime: String! + memoryTime: String! + ingress: String! + egress: String! + noRequests: String! + monthlyCost: String! +} + +type DeployAppVersionConnection { """ Pagination data for this connection. """ @@ -1176,24 +1511,22 @@ type PackageConnection { """ Contains the nodes in this connection. """ - edges: [PackageEdge]! -} + edges: [DeployAppVersionEdge]! -type PackageDistribution { - downloadUrl: String! - size: Int! - piritaDownloadUrl: String - piritaSize: Int! + """ + Total number of items in the connection. + """ + totalCount: Int } """ -A Relay edge containing a `Package` and its cursor. +A Relay edge containing a `DeployAppVersion` and its cursor. """ -type PackageEdge { +type DeployAppVersionEdge { """ The item at the end of the edge """ - node: Package + node: DeployAppVersion """ A cursor for use in pagination @@ -1201,32 +1534,12 @@ type PackageEdge { cursor: String! } -""" -Setup for backwards compatibility with existing frontends. -""" -interface PackageOwner { - globalName: String! -} - -type PackageTransferRequest implements Node { - """ - The ID of the object - """ - id: ID! - requestedBy: User! - previousOwnerObjectId: Int! - newOwnerObjectId: Int! - package: Package! - approvedBy: User - declinedBy: User - createdAt: DateTime! - expiresAt: DateTime! - closedAt: DateTime - previousOwner: PackageOwner! - newOwner: PackageOwner! +enum DeployAppVersionsSortBy { + NEWEST + OLDEST } -type PackageTransferRequestConnection { +type AppAliasConnection { """ Pagination data for this connection. """ @@ -1235,17 +1548,22 @@ type PackageTransferRequestConnection { """ Contains the nodes in this connection. """ - edges: [PackageTransferRequestEdge]! + edges: [AppAliasEdge]! + + """ + Total number of items in the connection. + """ + totalCount: Int } """ -A Relay edge containing a `PackageTransferRequest` and its cursor. +A Relay edge containing a `AppAlias` and its cursor. """ -type PackageTransferRequestEdge { +type AppAliasEdge { """ The item at the end of the edge """ - node: PackageTransferRequest + node: AppAlias """ A cursor for use in pagination @@ -1253,97 +1571,72 @@ type PackageTransferRequestEdge { cursor: String! } -type PackageVersion implements Node { +type AppAlias implements Node { + name: String! + app: DeployConfigInfo! + isDefault: Boolean! + """ The ID of the object """ id: ID! - package: Package! - version: String! - description: String! - manifest: String! - license: String - licenseFile: String - readme: String - witMd: String - repository: String - homepage: String + url: String! +} + +type DeployConfigInfo implements Node { + """ + The ID of the object + """ + id: ID! + createdBy: User! createdAt: DateTime! updatedAt: DateTime! - staticObjectsCompiled: Boolean! - nativeExecutablesCompiled: Boolean! - publishedBy: User! - signature: Signature - isArchived: Boolean! - file: String! + versions: [DeployConfigVersion] + publishedBy: User + namespace: String! + name: String! +} + +type DeployConfigVersion implements Node { + """ + The ID of the object + """ + id: ID! + signature: String + description: String + createdAt: DateTime! + + """ + """ + versionNumber: BigInt! + updatedAt: DateTime! + webcUrl: String! + @deprecated( + reason: "webc support for apps has been deprecated. Use DeployAppVersion.yamlConfig directly." + ) fileSize: BigInt! - piritaFile: String - piritaFileSize: BigInt! - piritaManifest: JSONString - piritaVolumes: JSONString - totalDownloads: Int! - pirita256hash: String - bindingsState: RegistryPackageVersionBindingsStateChoices! - nativeExecutablesState: RegistryPackageVersionNativeExecutablesStateChoices! - lastversionPackage( - offset: Int - before: String - after: String - first: Int - last: Int - ): PackageConnection! - commands: [Command!]! - nativeexecutableSet( - offset: Int - before: String - after: String - first: Int - last: Int - ): NativeExecutableConnection! - bindingsgeneratorSet( - offset: Int - before: String - after: String - first: Int - last: Int - ): BindingsGeneratorConnection! - javascriptlanguagebindingSet( - offset: Int - before: String - after: String - first: Int - last: Int - ): PackageVersionNPMBindingConnection! - pythonlanguagebindingSet( - offset: Int - before: String - after: String - first: Int - last: Int - ): PackageVersionPythonBindingConnection! - distribution: PackageDistribution! - filesystem: [PackageVersionFilesystem]! - isLastVersion: Boolean! - witFile: String - isSigned: Boolean! - moduleInterfaces: [InterfaceVersion!]! - modules: [PackageVersionModule!]! - getPiritaContents( - volume: String! = "atom" - root: String! = "" - ): [PiritaFilesystemItem!]! - nativeExecutables( - triple: String - wasmerCompilerVersion: String - ): [NativeExecutable] - bindings: [PackageVersionLanguageBinding]! - npmBindings: PackageVersionNPMBinding - pythonBindings: PackageVersionPythonBinding - hasBindings: Boolean! - hasCommands: Boolean! + @deprecated( + reason: "webc support for apps has been deprecated. Use DeployAppVersion.yamlConfig directly." + ) + fileOffset: BigInt! + @deprecated( + reason: "webc support for apps has been deprecated. Use DeployAppVersion.yamlConfig directly." + ) + config: String! + manifest: String + @deprecated( + reason: "webc support for apps as been deprecated. use DeployAppVersion.yamlConfig directly." + ) + info: DeployConfigInfo } -type PackageVersionConnection { +enum DeployAppsSortBy { + NEWEST + OLDEST + MOST_ACTIVE +} + +type NamespaceCollaboratorConnection { """ Pagination data for this connection. """ @@ -1352,17 +1645,17 @@ type PackageVersionConnection { """ Contains the nodes in this connection. """ - edges: [PackageVersionEdge]! + edges: [NamespaceCollaboratorEdge]! } """ -A Relay edge containing a `PackageVersion` and its cursor. +A Relay edge containing a `NamespaceCollaborator` and its cursor. """ -type PackageVersionEdge { +type NamespaceCollaboratorEdge { """ The item at the end of the edge """ - node: PackageVersion + node: NamespaceCollaborator """ A cursor for use in pagination @@ -1370,98 +1663,124 @@ type PackageVersionEdge { cursor: String! } -type PackageVersionFilesystem { - wasm: String! - host: String! +type PackageTransferRequestConnection { + """ + Pagination data for this connection. + """ + pageInfo: PageInfo! + + """ + Contains the nodes in this connection. + """ + edges: [PackageTransferRequestEdge]! } -interface PackageVersionLanguageBinding { - id: ID! - language: ProgrammingLanguage! +""" +A Relay edge containing a `PackageTransferRequest` and its cursor. +""" +type PackageTransferRequestEdge { + """ + The item at the end of the edge + """ + node: PackageTransferRequest """ - The URL of the generated artifacts on WAPM's CDN. + A cursor for use in pagination """ - url: String! + cursor: String! +} +type APITokenConnection { """ - When the binding was generated + Pagination data for this connection. """ - createdAt: DateTime! + pageInfo: PageInfo! """ - Package version used to generate this binding + Contains the nodes in this connection. """ - generator: BindingsGenerator! - name: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) - kind: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) + edges: [APITokenEdge]! +} +""" +A Relay edge containing a `APIToken` and its cursor. +""" +type APITokenEdge { """ - Name of package source + The item at the end of the edge """ - packageName: String! - module: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) + node: APIToken + + """ + A cursor for use in pagination + """ + cursor: String! } -type PackageVersionModule { - name: String! - source: String! - abi: String - publicUrl: String! +type APIToken { + id: ID! + user: User! + identifier: String + createdAt: DateTime! + revokedAt: DateTime + lastUsedAt: DateTime + nonceSet( + offset: Int + before: String + after: String + first: Int + last: Int + ): NonceConnection! } -type PackageVersionNPMBinding implements PackageVersionLanguageBinding & Node { +type NonceConnection { """ - The ID of the object + Pagination data for this connection. """ - id: ID! - language: ProgrammingLanguage! + pageInfo: PageInfo! """ - The URL of the generated artifacts on WAPM's CDN. + Contains the nodes in this connection. """ - url: String! + edges: [NonceEdge]! """ - When the binding was generated + Total number of items in the connection. """ - createdAt: DateTime! + totalCount: Int +} +""" +A Relay edge containing a `Nonce` and its cursor. +""" +type NonceEdge { """ - Package version used to generate this binding + The item at the end of the edge """ - generator: BindingsGenerator! - name: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) - kind: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) + node: Nonce """ - Name of package source + A cursor for use in pagination """ - packageName: String! - module: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) - npmDefaultInstallPackageName(url: String): String! - @deprecated(reason: "Please use packageName instead") + cursor: String! } -type PackageVersionNPMBindingConnection { +type Nonce implements Node { + """ + The ID of the object + """ + id: ID! + name: String! + callbackUrl: String! + createdAt: DateTime! + isValidated: Boolean! + secret: String! + token: String! + expired: Boolean! + authUrl: String! +} + +type UserNotificationConnection { """ Pagination data for this connection. """ @@ -1470,17 +1789,18 @@ type PackageVersionNPMBindingConnection { """ Contains the nodes in this connection. """ - edges: [PackageVersionNPMBindingEdge]! + edges: [UserNotificationEdge]! + hasPendingNotifications: Boolean! } """ -A Relay edge containing a `PackageVersionNPMBinding` and its cursor. +A Relay edge containing a `UserNotification` and its cursor. """ -type PackageVersionNPMBindingEdge { +type UserNotificationEdge { """ The item at the end of the edge """ - node: PackageVersionNPMBinding + node: UserNotification """ A cursor for use in pagination @@ -1488,198 +1808,301 @@ type PackageVersionNPMBindingEdge { cursor: String! } -type PackageVersionPythonBinding implements PackageVersionLanguageBinding & Node { +type UserNotification implements Node { """ The ID of the object """ id: ID! - language: ProgrammingLanguage! + icon: String + body: UserNotificationBody! + seenState: UserNotificationSeenState! + kind: UserNotificationKind + createdAt: DateTime! +} - """ - The URL of the generated artifacts on WAPM's CDN. - """ - url: String! +type UserNotificationBody { + text: String! + ranges: [NodeBodyRange]! +} + +enum UserNotificationSeenState { + UNSEEN + SEEN + SEEN_AND_READ +} + +union UserNotificationKind = + UserNotificationKindPublishedPackageVersion + | UserNotificationKindIncomingPackageTransfer + | UserNotificationKindIncomingPackageInvite + | UserNotificationKindIncomingNamespaceInvite + +type UserNotificationKindPublishedPackageVersion { + packageVersion: PackageVersion! +} + +type UserNotificationKindIncomingNamespaceInvite { + namespaceInvite: NamespaceCollaboratorInvite! +} + +type Signature { + id: ID! + publicKey: PublicKey! + data: String! + createdAt: DateTime! +} + +type UserNotificationKindIncomingPackageTransfer { + packageTransferRequest: PackageTransferRequest! +} + +type UserNotificationKindIncomingPackageInvite { + packageInvite: PackageCollaboratorInvite! +} + +input DeploymentV1 { + name: String! + workload: WorkloadV1! +} + +input WorkloadV1 { + capability: CapabilityMapV1 + name: String = null + runner: WorkloadRunnerV1! +} + +input AppV1Spec { + aliases: [String] = [] + workload: WorkloadV2! +} + +input WorkloadV2 { + source: String! +} + +input CapabilityCpuV1 { + maximumThreads: Int + maximumUsage: Int +} + +input FileSystemPermissionsV1 { + delete: Boolean + read: Boolean + write: Boolean +} + +input FileSystemVolumeMountV1 { + path: String! + permissions: [FileSystemPermissionsV1] +} + +input FileSystemVolumeSourceLocalV1 { + maximumSize: String! +} + +input FileSystemVolumeSourceV1 { + local: FileSystemVolumeSourceLocalV1! +} + +input FileSystemVolumeConfigV1 { + mounts: [FileSystemVolumeMountV1]! + name: String! + source: FileSystemVolumeSourceV1! +} + +input CapabilityFileSystemV1 { + volumes: [FileSystemVolumeConfigV1]! +} + +input CapabilityPersistentMemoryV1 { + volumes: [String] +} + +input CapabilityMemorySwapV1 { + maximumSize: String + memoryId: String +} + +input CapabilityNetworkV1 { + egress: NetworkEgressV1 +} + +input NetworkEgressV1 { + enabled: Boolean +} + +input CapabilityNetworkDnsV1 { + enabled: Boolean + servers: [String] + allowedHosts: NetworkDnsAllowedHostsV1 +} + +input NetworkDnsAllowedHostsV1 { + allowAllHosts: Boolean + hosts: [String] + regexPatterns: [String] + wildcardPatterns: [String] +} + +input CapabilityNetworkGatewayV1 { + domains: [String] + enforceHttps: Boolean +} + +input CapabilityMapV1 { + memorySwap: CapabilityCpuV1 +} + +input WebcSourceV1 { + name: String! + namespace: String! + repository: String! = "https://registry.wasmer.wtf" + tag: String + authToken: String +} + +input WorkloadRunnerV1 { + webProxy: RunnerWebProxyV1 + wcgi: RunnerWCGIV1 +} + +""" +Run a webassembly file. +""" +input RunnerWCGIV1 { + source: WorkloadRunnerWasmSourceV1! + dialect: String +} + +input RunnerWebProxyV1 { + source: WorkloadRunnerWasmSourceV1! +} + +input WorkloadRunnerWasmSourceV1 { + webc: WebcSourceV1! +} + +type StripeCustomer { + id: ID! +} + +type Billing { + stripeCustomer: StripeCustomer! + payments: [PaymentIntent]! + paymentMethods: [PaymentMethod]! +} +type PaymentIntent implements Node { """ - When the binding was generated + Three-letter ISO currency code """ - createdAt: DateTime! + currency: String! """ - Package version used to generate this binding + Status of this PaymentIntent, one of requires_payment_method, requires_confirmation, requires_action, processing, requires_capture, canceled, or succeeded. You can read more about PaymentIntent statuses here. """ - generator: BindingsGenerator! - name: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) - kind: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) + status: DjstripePaymentIntentStatusChoices! """ - Name of package source + The ID of the object """ - packageName: String! - module: String! - @deprecated( - reason: "Do not use this field, since bindings for all modules are generated at once now." - ) - pythonDefaultInstallPackageName(url: String): String! + id: ID! + amount: String! } -type PackageVersionPythonBindingConnection { - """ - Pagination data for this connection. - """ - pageInfo: PageInfo! - +enum DjstripePaymentIntentStatusChoices { """ - Contains the nodes in this connection. + Cancellation invalidates the intent for future confirmation and cannot be undone. """ - edges: [PackageVersionPythonBindingEdge]! -} + CANCELED -""" -A Relay edge containing a `PackageVersionPythonBinding` and its cursor. -""" -type PackageVersionPythonBindingEdge { """ - The item at the end of the edge + Required actions have been handled. """ - node: PackageVersionPythonBinding + PROCESSING """ - A cursor for use in pagination + Payment Method require additional action, such as 3D secure. """ - cursor: String! -} + REQUIRES_ACTION -""" -The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. -""" -type PageInfo { """ - When paginating forwards, are there more items? + Capture the funds on the cards which have been put on holds. """ - hasNextPage: Boolean! + REQUIRES_CAPTURE """ - When paginating backwards, are there more items? + Intent is ready to be confirmed. """ - hasPreviousPage: Boolean! + REQUIRES_CONFIRMATION """ - When paginating backwards, the cursor to continue. + Intent created and requires a Payment Method to be attached. """ - startCursor: String + REQUIRES_PAYMENT_METHOD """ - When paginating forwards, the cursor to continue. + The funds are in your account. """ - endCursor: String -} - -type PiritaFilesystemDir { - name(display: PiritaFilesystemNameDisplay): String! -} - -type PiritaFilesystemFile { - name(display: PiritaFilesystemNameDisplay): String! - size: Int! - offset: Int! -} - -union PiritaFilesystemItem = PiritaFilesystemFile | PiritaFilesystemDir - -enum PiritaFilesystemNameDisplay { - RELATIVE - ABSOLUTE + SUCCEEDED } -enum ProgrammingLanguage { - PYTHON - JAVASCRIPT -} +union PaymentMethod = CardPaymentMethod -type PublicKey implements Node { +type CardPaymentMethod implements Node { """ The ID of the object """ id: ID! - owner: User! - keyId: String! - key: String! - revokedAt: DateTime - uploadedAt: DateTime! - verifyingSignature: Signature - revoked: Boolean! -} - -input PublishDeployAppInput { - config: JSONString! - name: ID - owner: ID - description: String - clientMutationId: String + brand: CardBrand! + country: String! + expMonth: Int! + expYear: Int! + funding: CardFunding! + last4: String! + isDefault: Boolean! } -type PublishDeployAppPayload { - deployAppVersion: DeployAppVersion! - clientMutationId: String -} - -input PublishDeployConfigInput { - content: String! - name: ID - description: String - clientMutationId: String -} +""" +Card brand. -type PublishDeployConfigPayload { - deployConfigVersion: DeployConfigVersion! - clientMutationId: String +Can be amex, diners, discover, jcb, mastercard, unionpay, visa, or unknown. +""" +enum CardBrand { + AMEX + DINERS + DISCOVER + JCB + MASTERCARD + UNIONPAY + VISA + UNKNOWN } -input PublishPackageInput { - name: String! - version: String! - description: String! - manifest: String! - license: String - licenseFile: String - readme: String - repository: String - homepage: String - file: String - signedUrl: String - signature: InputSignature - - """ - The package icon - """ - icon: String - clientMutationId: String -} +""" +Card funding type. -type PublishPackagePayload { - success: Boolean! - packageVersion: PackageVersion! - clientMutationId: String +Can be credit, debit, prepaid, or unknown. +""" +enum CardFunding { + CREDIT + DEBIT + PREPAID + UNKNOWN } -input PublishPublicKeyInput { - keyId: String! - key: String! - verifyingSignatureId: String - clientMutationId: String +type Payment { + id: ID + amount: String + paidOn: DateTime } -type PublishPublicKeyPayload { - success: Boolean! - publicKey: PublicKey! - clientMutationId: String +""" +Log entry for deploy app. +""" +type Log { + timestamp: Float! + message: String! } type Query { @@ -1716,9 +2139,20 @@ type Query { version: String ): DeployAppVersion getDeployApp(name: String!, owner: String!): DeployApp + getAppByGlobalAlias(alias: String!): DeployApp + getDeployApps( + sortBy: DeployAppsSortBy + updatedAfter: DateTime + offset: Int + before: String + after: String + first: Int + last: Int + ): DeployAppConnection! viewer: User getUser(username: String!): User getPasswordResetToken(token: String!): GetPasswordResetToken + getAuthNonce(name: String!): Nonce packages( before: String after: String @@ -1794,209 +2228,537 @@ type Query { """ id: ID! ): Node + info: RegistryInfo } -input ReadNotificationInput { - notificationId: ID! - clientMutationId: String +type GetPasswordResetToken { + valid: Boolean! + user: User } -type ReadNotificationPayload { - notification: UserNotification - clientMutationId: String -} +type CollectionConnection { + """ + Pagination data for this connection. + """ + pageInfo: PageInfo! -type Refresh { - payload: GenericScalar! - refreshExpiresIn: Int! - token: String! - refreshToken: String! + """ + Contains the nodes in this connection. + """ + edges: [CollectionEdge]! } -input RegisterUserInput { - fullName: String! - email: String! - username: String! - password: String! - clientMutationId: String +""" +A Relay edge containing a `Collection` and its cursor. +""" +type CollectionEdge { + """ + The item at the end of the edge + """ + node: Collection + + """ + A cursor for use in pagination + """ + cursor: String! } -type RegisterUserPayload { - token: String - clientMutationId: String +type SignedUrl { + url: String! } -enum RegistryNamespaceMaintainerInviteRoleChoices { +type SearchConnection { """ - Admin + Pagination data for this connection. """ - ADMIN + pageInfo: PageInfo! """ - Editor + Contains the nodes in this connection. """ - EDITOR + edges: [SearchEdge]! +} + +""" +A Relay edge containing a `Search` and its cursor. +""" +type SearchEdge { + """ + The item at the end of the edge + """ + node: SearchResult """ - Viewer + A cursor for use in pagination """ - VIEWER + cursor: String! } -enum RegistryNamespaceMaintainerRoleChoices { +union SearchResult = PackageVersion | User | Namespace + +enum SearchOrderBy { + ALPHABETICALLY + SIZE + TOTAL_DOWNLOADS + PUBLISHED_DATE +} + +enum SearchOrderSort { + ASC + DESC +} + +enum SearchKind { + PACKAGE + NAMESPACE + USER +} + +enum SearchPublishDate { + LAST_DAY + LAST_WEEK + LAST_MONTH + LAST_YEAR +} + +union GlobalObject = User | Namespace + +type RegistryInfo { """ - Admin + Base URL for this registry + """ + baseUrl: String! + + """ + Base URL for the default frontend + """ + defaultFrontend: String! + + """ + URL to the graphql endpoint """ - ADMIN + graphqlUrl: String! """ - Editor + Public metadata about packages """ - EDITOR + packages: PackageInfo! """ - Viewer + Public metadata about the graphql schema """ - VIEWER + schema: SchemaInfo! } -enum RegistryPackageMaintainerInviteRoleChoices { +type PackageInfo { """ - Admin + Number of package versions published this month """ - ADMIN + versionsPublishedThisMonth: Int! """ - Editor + Number of new packages published this month """ - EDITOR + newPackagesThisMonth: Int! """ - Viewer + Number of package downloads this month """ - VIEWER + packageDownloadsThisMonth: Int! } -enum RegistryPackageMaintainerRoleChoices { +type SchemaInfo { """ - Admin + Download link for graphql schema """ - ADMIN + downloadUrl: String! """ - Editor + SHA256 hash of the schema data """ - EDITOR + SHA256Hash: String! """ - Viewer + Timestamp when the schema was last updated """ - VIEWER + lastUpdated: DateTime! } -enum RegistryPackageVersionBindingsStateChoices { - """ - Bindings are not detected - """ - NOT_PRESENT +type Mutation { + publishDeployConfig( + input: PublishDeployConfigInput! + ): PublishDeployConfigPayload + @deprecated(reason: "Please use publishDeployApp instead.") + publishDeployApp(input: PublishDeployAppInput!): PublishDeployAppPayload """ - Bindings are being built + Add current user to the waitlist. """ - GENERATING + joinWaitlist(input: JoinWaitlistInput!): JoinWaitlistPayload """ - Bindings generation has failed + Add stripe payment to the user """ - ERROR + addPayment(input: AddPaymentInput!): AddPaymentPayload """ - Bindings are built and present + Mutation to change the active version of a DeployApp to another DeployAppVersion. """ - GENERATED_AND_PRESENT -} + markAppVersionAsActive( + input: MarkAppVersionAsActiveInput! + ): MarkAppVersionAsActivePayload -enum RegistryPackageVersionNativeExecutablesStateChoices { """ - Native Executables are not detected + Set a payment method as default for the user. """ - NOT_PRESENT + makePaymentDefault( + input: SetDefaultPaymentMethodInput! + ): SetDefaultPaymentMethodPayload """ - Native Executables are being built + Try to detach a payment method from customer. + Fails if trying to detach a default method, + or if it's the only payment method. """ - GENERATING + detachPaymentMethod( + input: DetachPaymentMethodInput! + ): DetachPaymentMethodPayload + generateDeployConfigToken( + input: GenerateDeployConfigTokenInput! + ): GenerateDeployConfigTokenPayload + tokenAuth(input: ObtainJSONWebTokenInput!): ObtainJSONWebTokenPayload + generateDeployToken( + input: GenerateDeployTokenInput! + ): GenerateDeployTokenPayload + verifyAccessToken(token: String): Verify + refreshAccessToken(refreshToken: String): Refresh + revokeAccessToken(refreshToken: String): Revoke + registerUser(input: RegisterUserInput!): RegisterUserPayload + socialAuth(input: SocialAuthJWTInput!): SocialAuthJWTPayload + validateUserEmail(input: ValidateUserEmailInput!): ValidateUserEmailPayload + requestPasswordReset( + input: RequestPasswordResetInput! + ): RequestPasswordResetPayload + requestValidationEmail( + input: RequestValidationEmailInput! + ): RequestValidationEmailPayload + changeUserPassword(input: ChangeUserPasswordInput!): ChangeUserPasswordPayload + changeUserUsername(input: ChangeUserUsernameInput!): ChangeUserUsernamePayload + changeUserEmail(input: ChangeUserEmailInput!): ChangeUserEmailPayload + updateUserInfo(input: UpdateUserInfoInput!): UpdateUserInfoPayload + validateUserPassword( + input: ValidateUserPasswordInput! + ): ValidateUserPasswordPayload + generateApiToken(input: GenerateAPITokenInput!): GenerateAPITokenPayload + revokeApiToken(input: RevokeAPITokenInput!): RevokeAPITokenPayload + checkUserExists(input: CheckUserExistsInput!): CheckUserExistsPayload + readNotification(input: ReadNotificationInput!): ReadNotificationPayload + seePendingNotifications( + input: SeePendingNotificationsInput! + ): SeePendingNotificationsPayload + newNonce(input: NewNonceInput!): NewNoncePayload + validateNonce(input: ValidateNonceInput!): ValidateNoncePayload + publishPublicKey(input: PublishPublicKeyInput!): PublishPublicKeyPayload + publishPackage(input: PublishPackageInput!): PublishPackagePayload + updatePackage(input: UpdatePackageInput!): UpdatePackagePayload + likePackage(input: LikePackageInput!): LikePackagePayload + unlikePackage(input: UnlikePackageInput!): UnlikePackagePayload + watchPackage(input: WatchPackageInput!): WatchPackagePayload + unwatchPackage(input: UnwatchPackageInput!): UnwatchPackagePayload + archivePackage(input: ArchivePackageInput!): ArchivePackagePayload + changePackageVersionArchivedStatus( + input: ChangePackageVersionArchivedStatusInput! + ): ChangePackageVersionArchivedStatusPayload + createNamespace(input: CreateNamespaceInput!): CreateNamespacePayload + updateNamespace(input: UpdateNamespaceInput!): UpdateNamespacePayload + deleteNamespace(input: DeleteNamespaceInput!): DeleteNamespacePayload + inviteNamespaceCollaborator( + input: InviteNamespaceCollaboratorInput! + ): InviteNamespaceCollaboratorPayload + acceptNamespaceCollaboratorInvite( + input: AcceptNamespaceCollaboratorInviteInput! + ): AcceptNamespaceCollaboratorInvitePayload + removeNamespaceCollaboratorInvite( + input: RemoveNamespaceCollaboratorInviteInput! + ): RemoveNamespaceCollaboratorInvitePayload + removeNamespaceCollaborator( + input: RemoveNamespaceCollaboratorInput! + ): RemoveNamespaceCollaboratorPayload + updateNamespaceCollaboratorRole( + input: UpdateNamespaceCollaboratorRoleInput! + ): UpdateNamespaceCollaboratorRolePayload + updateNamespaceCollaboratorInviteRole( + input: UpdateNamespaceCollaboratorInviteRoleInput! + ): UpdateNamespaceCollaboratorInviteRolePayload + invitePackageCollaborator( + input: InvitePackageCollaboratorInput! + ): InvitePackageCollaboratorPayload + acceptPackageCollaboratorInvite( + input: AcceptPackageCollaboratorInviteInput! + ): AcceptPackageCollaboratorInvitePayload + removePackageCollaboratorInvite( + input: RemovePackageCollaboratorInviteInput! + ): RemovePackageCollaboratorInvitePayload + updatePackageCollaboratorRole( + input: UpdatePackageCollaboratorRoleInput! + ): UpdatePackageCollaboratorRolePayload + updatePackageCollaboratorInviteRole( + input: UpdatePackageCollaboratorInviteRoleInput! + ): UpdatePackageCollaboratorInviteRolePayload + removePackageCollaborator( + input: RemovePackageCollaboratorInput! + ): RemovePackageCollaboratorPayload + requestPackageTransfer( + input: RequestPackageTransferInput! + ): RequestPackageTransferPayload + acceptPackageTransferRequest( + input: AcceptPackageTransferRequestInput! + ): AcceptPackageTransferRequestPayload + removePackageTransferRequest( + input: RemovePackageTransferRequestInput! + ): RemovePackageTransferRequestPayload + generateBindingsForAllPackages( + input: GenerateBindingsForAllPackagesInput! + ): GenerateBindingsForAllPackagesPayload +} - """ - Native Executables generation has failed - """ - ERROR +type PublishDeployConfigPayload { + deployConfigVersion: DeployConfigVersion! + clientMutationId: String +} + +input PublishDeployConfigInput { + content: String! + name: ID + description: String + clientMutationId: String +} + +type PublishDeployAppPayload { + deployAppVersion: DeployAppVersion! + clientMutationId: String +} + +input PublishDeployAppInput { + config: Configuration! + name: ID + owner: ID + description: String + makeDefault: Boolean = true + clientMutationId: String +} + +input Configuration { + deployment: AppV0 + yamlConfig: String +} + +input AppV0 { + kind: String = "wasmer.io/App.v0" + appId: ID + name: String! + description: String + package: String! +} + +""" +Add current user to the waitlist. +""" +type JoinWaitlistPayload { + waitlistMember: WaitlistMember! + clientMutationId: String +} + +input JoinWaitlistInput { + name: String! + clientMutationId: String +} + +""" +Add stripe payment to the user +""" +type AddPaymentPayload { + customerSecret: String! + clientMutationId: String +} + +input AddPaymentInput { + clientMutationId: String +} + +""" +Mutation to change the active version of a DeployApp to another DeployAppVersion. +""" +type MarkAppVersionAsActivePayload { + app: DeployApp! + clientMutationId: String +} +input MarkAppVersionAsActiveInput { """ - Native Executables are built and present + The ID of the DeployAppVersion to set as the new active version. """ - GENERATED_AND_PRESENT + appVersion: ID! + clientMutationId: String +} + +""" +Set a payment method as default for the user. +""" +type SetDefaultPaymentMethodPayload { + success: Boolean! + billing: Billing! + clientMutationId: String +} + +input SetDefaultPaymentMethodInput { + paymentMethod: ID! + clientMutationId: String +} + +""" +Try to detach a payment method from customer. +Fails if trying to detach a default method, +or if it's the only payment method. +""" +type DetachPaymentMethodPayload { + success: Boolean! + billing: Billing! + clientMutationId: String +} + +input DetachPaymentMethodInput { + paymentMethod: ID! + clientMutationId: String +} + +type GenerateDeployConfigTokenPayload { + token: String! + config: String! + clientMutationId: String +} + +input GenerateDeployConfigTokenInput { + config: String! + clientMutationId: String +} + +type ObtainJSONWebTokenPayload { + payload: GenericScalar! + refreshExpiresIn: Int! + username: CaseInsensitiveString! + clientMutationId: String + token: String! + refreshToken: String! +} + +""" +The `GenericScalar` scalar type represents a generic +GraphQL scalar value that could be: +String, Boolean, Int, Float, List or Object. +""" +scalar GenericScalar + +""" +The `CaseInsensitiveString` scalar type represents textual data, represented as UTF-8 +character sequences. The String type is most often used by GraphQL to +represent free-form human-readable text. +""" +scalar CaseInsensitiveString + +input ObtainJSONWebTokenInput { + clientMutationId: String + username: String! + password: String! +} + +type GenerateDeployTokenPayload { + token: String! + deployConfigVersion: DeployConfigVersion! + clientMutationId: String +} + +input GenerateDeployTokenInput { + deployConfigVersionId: String! + clientMutationId: String } -input RemoveNamespaceCollaboratorInput { - namespaceCollaboratorId: ID! - clientMutationId: String +type Verify { + payload: GenericScalar! } -input RemoveNamespaceCollaboratorInviteInput { - inviteId: ID! - clientMutationId: String +type Refresh { + payload: GenericScalar! + refreshExpiresIn: Int! + token: String! + refreshToken: String! } -type RemoveNamespaceCollaboratorInvitePayload { - namespace: Namespace! - clientMutationId: String +type Revoke { + revoked: Int! } -type RemoveNamespaceCollaboratorPayload { - namespace: Namespace! +type RegisterUserPayload { + token: String clientMutationId: String } -input RemovePackageCollaboratorInput { - packageCollaboratorId: ID! +input RegisterUserInput { + fullName: String! + email: String! + username: CaseInsensitiveString! + password: String! clientMutationId: String } -input RemovePackageCollaboratorInviteInput { - inviteId: ID! +type SocialAuthJWTPayload { + social: SocialAuth + token: String clientMutationId: String } -type RemovePackageCollaboratorInvitePayload { - package: Package! - clientMutationId: String +type SocialAuth implements Node { + """ + The ID of the object + """ + id: ID! + user: User! + provider: String! + uid: String! + extraData: String! + created: DateTime! + modified: DateTime! } -type RemovePackageCollaboratorPayload { - package: Package! +input SocialAuthJWTInput { + provider: String! + accessToken: String! clientMutationId: String } -input RemovePackageTransferRequestInput { - packageTransferRequestId: ID! +type ValidateUserEmailPayload { + user: User clientMutationId: String } -type RemovePackageTransferRequestPayload { - package: Package! +input ValidateUserEmailInput { + """ + The user id + """ + userId: ID + challenge: String! clientMutationId: String } -input RequestPackageTransferInput { - packageId: ID! - newOwnerId: ID! +type RequestPasswordResetPayload { + email: String! + errors: [ErrorType] clientMutationId: String } -type RequestPackageTransferPayload { - package: Package! - clientMutationId: String +type ErrorType { + field: String! + messages: [String!]! } input RequestPasswordResetInput { @@ -2004,9 +2766,9 @@ input RequestPasswordResetInput { clientMutationId: String } -type RequestPasswordResetPayload { - email: String! - errors: [ErrorType] +type RequestValidationEmailPayload { + user: User + success: Boolean! clientMutationId: String } @@ -2018,526 +2780,556 @@ input RequestValidationEmailInput { clientMutationId: String } -type RequestValidationEmailPayload { - user: User - success: Boolean! +type ChangeUserPasswordPayload { + token: String clientMutationId: String } -type Revoke { - revoked: Int! +input ChangeUserPasswordInput { + """ + The token associated to change the password. If not existing it will use the request user by default + """ + token: String + oldPassword: String + password: String! + clientMutationId: String } -input RevokeAPITokenInput { +type ChangeUserUsernamePayload { + user: User + token: String + clientMutationId: String +} + +input ChangeUserUsernameInput { """ - The API token ID + The new user username """ - tokenId: ID! + username: CaseInsensitiveString! clientMutationId: String } -type RevokeAPITokenPayload { - token: APIToken - success: Boolean +type ChangeUserEmailPayload { + user: User! clientMutationId: String } -type SearchConnection { +input ChangeUserEmailInput { + newEmail: String! + clientMutationId: String +} + +type UpdateUserInfoPayload { + user: User + clientMutationId: String +} + +input UpdateUserInfoInput { """ - Pagination data for this connection. + The user id """ - pageInfo: PageInfo! + userId: ID """ - Contains the nodes in this connection. + The user full name """ - edges: [SearchEdge]! -} + fullName: String -""" -A Relay edge containing a `Search` and its cursor. -""" -type SearchEdge { """ - The item at the end of the edge + The user bio """ - node: SearchResult + bio: String """ - A cursor for use in pagination + The user avatar """ - cursor: String! -} + avatar: String -enum SearchKind { - PACKAGE - NAMESPACE - USER -} + """ + The user Twitter (it can be the url, or the handle with or without the @) + """ + twitter: String -enum SearchOrderBy { - ALPHABETICALLY - SIZE - TOTAL_DOWNLOADS - PUBLISHED_DATE + """ + The user Github (it can be the url, or the handle with or without the @) + """ + github: String + + """ + The user website (it must be a valid url) + """ + websiteUrl: String + + """ + The user location + """ + location: String + clientMutationId: String } -enum SearchOrderSort { - ASC - DESC +type ValidateUserPasswordPayload { + success: Boolean + clientMutationId: String } -enum SearchPublishDate { - LAST_DAY - LAST_WEEK - LAST_MONTH - LAST_YEAR +input ValidateUserPasswordInput { + password: String! + clientMutationId: String } -union SearchResult = PackageVersion | User | Namespace +type GenerateAPITokenPayload { + token: APIToken + tokenRaw: String + user: User + clientMutationId: String +} -input SeePendingNotificationsInput { +input GenerateAPITokenInput { + identifier: String clientMutationId: String } -type SeePendingNotificationsPayload { +type RevokeAPITokenPayload { + token: APIToken success: Boolean clientMutationId: String } -type Signature { - id: ID! - publicKey: PublicKey! - data: String! - createdAt: DateTime! +input RevokeAPITokenInput { + """ + The API token ID + """ + tokenId: ID! + clientMutationId: String } -type SignedUrl { - url: String! -} +type CheckUserExistsPayload { + exists: Boolean! -type SocialAuth implements Node { """ - The ID of the object + The user is only returned if the user input was the username """ - id: ID! - user: User! - provider: String! - uid: String! - extraData: String! - created: DateTime! - modified: DateTime! + user: User + clientMutationId: String } -input SocialAuthJWTInput { - provider: String! - accessToken: String! +input CheckUserExistsInput { + """ + The user + """ + user: String! clientMutationId: String } -type SocialAuthJWTPayload { - social: SocialAuth - token: String +type ReadNotificationPayload { + notification: UserNotification clientMutationId: String } -type Subscription { - usageMetrics: [UsageMetric]! - packageVersionCreated(publishedBy: ID, ownerId: ID): PackageVersion! - userNotificationCreated(userId: ID!): UserNotificationCreated! +input ReadNotificationInput { + notificationId: ID! + clientMutationId: String } -input UnlikePackageInput { - packageId: ID! +type SeePendingNotificationsPayload { + success: Boolean + clientMutationId: String +} + +input SeePendingNotificationsInput { clientMutationId: String } -type UnlikePackagePayload { - package: Package! +type NewNoncePayload { + nonce: Nonce! clientMutationId: String } -input UnwatchPackageInput { - packageId: ID! +input NewNonceInput { + name: String! + callbackUrl: String! clientMutationId: String } -type UnwatchPackagePayload { - package: Package! +type ValidateNoncePayload { + nonce: Nonce! clientMutationId: String } -input UpdateNamespaceCollaboratorInviteRoleInput { - namespaceCollaboratorInviteId: ID! - role: GrapheneRole! +input ValidateNonceInput { + id: ID! + secret: String! clientMutationId: String } -type UpdateNamespaceCollaboratorInviteRolePayload { - collaboratorInvite: NamespaceCollaboratorInvite! +type PublishPublicKeyPayload { + success: Boolean! + publicKey: PublicKey! clientMutationId: String } -input UpdateNamespaceCollaboratorRoleInput { - namespaceCollaboratorId: ID! - role: GrapheneRole! +input PublishPublicKeyInput { + keyId: String! + key: String! + verifyingSignatureId: String clientMutationId: String } -type UpdateNamespaceCollaboratorRolePayload { - collaborator: NamespaceCollaborator! +type PublishPackagePayload { + success: Boolean! + packageVersion: PackageVersion! clientMutationId: String } -input UpdateNamespaceInput { - namespaceId: ID! +input PublishPackageInput { + name: String! + version: String! + description: String! + manifest: String! + license: String + licenseFile: String + readme: String + repository: String + homepage: String + file: String + signedUrl: String + signature: InputSignature """ - The namespace slug name + The package icon """ - name: String + icon: String + clientMutationId: String +} - """ - The namespace display name - """ - displayName: String +input InputSignature { + publicKeyKeyId: String! + data: String! +} - """ - The namespace description - """ - description: String +type UpdatePackagePayload { + package: Package! + clientMutationId: String +} + +input UpdatePackageInput { + packageId: ID! """ - The namespace avatar + The package icon """ - avatar: String + icon: String clientMutationId: String } -type UpdateNamespacePayload { - namespace: Namespace! +type LikePackagePayload { + package: Package! clientMutationId: String } -input UpdatePackageCollaboratorInviteRoleInput { - packageCollaboratorInviteId: ID! - role: GrapheneRole! +input LikePackageInput { + packageId: ID! clientMutationId: String } -type UpdatePackageCollaboratorInviteRolePayload { - collaboratorInvite: PackageCollaboratorInvite! +type UnlikePackagePayload { + package: Package! clientMutationId: String } -input UpdatePackageCollaboratorRoleInput { - packageCollaboratorId: ID! - role: GrapheneRole! +input UnlikePackageInput { + packageId: ID! clientMutationId: String } -type UpdatePackageCollaboratorRolePayload { - collaborator: PackageCollaborator! +type WatchPackagePayload { + package: Package! clientMutationId: String } -input UpdatePackageInput { +input WatchPackageInput { packageId: ID! - - """ - The package icon - """ - icon: String clientMutationId: String } -type UpdatePackagePayload { +type UnwatchPackagePayload { package: Package! clientMutationId: String } -input UpdateUserInfoInput { - """ - The user id - """ - userId: ID - - """ - The user full name - """ - fullName: String - - """ - The user bio - """ - bio: String - - """ - The user avatar - """ - avatar: String - - """ - The user Twitter (it can be the url, or the handle with or without the @) - """ - twitter: String +input UnwatchPackageInput { + packageId: ID! + clientMutationId: String +} - """ - The user Github (it can be the url, or the handle with or without the @) - """ - github: String +type ArchivePackagePayload { + package: Package! + clientMutationId: String +} - """ - The user website (it must be a valid url) - """ - websiteUrl: String +input ArchivePackageInput { + packageId: ID! + clientMutationId: String +} - """ - The user location - """ - location: String +type ChangePackageVersionArchivedStatusPayload { + packageVersion: PackageVersion! clientMutationId: String } -type UpdateUserInfoPayload { - user: User +input ChangePackageVersionArchivedStatusInput { + packageVersionId: ID! + isArchived: Boolean clientMutationId: String } -type UsageMetric { - metricType: String! - metricValue: String! - metricDatetime: DateTime! +type CreateNamespacePayload { + namespace: Namespace! + user: User! + clientMutationId: String } -type User implements Node & PackageOwner & Owner { +input CreateNamespaceInput { + name: String! + """ - Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. + The namespace display name """ - username: String! - firstName: String! - lastName: String! - email: String! - dateJoined: DateTime! - isEmailValidated: Boolean! - bio: String - location: String - websiteUrl: String + displayName: String """ - The ID of the object + The namespace description """ - id: ID! - globalName: String! - avatar(size: Int = 80): String! - isViewer: Boolean! - hasUsablePassword: Boolean - fullName: String! - githubUrl: String - twitterUrl: String - publicActivity( - before: String - after: String - first: Int - last: Int - ): ActivityEventConnection! - namespaces( - before: String - after: String - first: Int - last: Int - ): NamespaceConnection! - packages( - collaborating: Boolean = false - before: String - after: String - first: Int - last: Int - ): PackageConnection! - apps( - collaborating: Boolean = false - before: String - after: String - first: Int - last: Int - ): AppConnection! - packageVersions( - before: String - after: String - first: Int - last: Int - ): PackageVersionConnection! - packageTransfersIncoming( - before: String - after: String - first: Int - last: Int - ): PackageTransferRequestConnection! - packageInvitesIncoming( - before: String - after: String - first: Int - last: Int - ): PackageCollaboratorInviteConnection! - namespaceInvitesIncoming( - before: String - after: String - first: Int - last: Int - ): NamespaceCollaboratorInviteConnection! - apiTokens( - before: String - after: String - first: Int - last: Int - ): APITokenConnection! - notifications( - before: String - after: String - first: Int - last: Int - ): UserNotificationConnection! + description: String + + """ + The namespace avatar + """ + avatar: String + clientMutationId: String } -type UserConnection { +type UpdateNamespacePayload { + namespace: Namespace! + clientMutationId: String +} + +input UpdateNamespaceInput { + namespaceId: ID! + """ - Pagination data for this connection. + The namespace slug name """ - pageInfo: PageInfo! + name: String """ - Contains the nodes in this connection. + The namespace display name """ - edges: [UserEdge]! -} + displayName: String -""" -A Relay edge containing a `User` and its cursor. -""" -type UserEdge { """ - The item at the end of the edge + The namespace description """ - node: User + description: String """ - A cursor for use in pagination + The namespace avatar """ - cursor: String! + avatar: String + clientMutationId: String } -type UserNotification implements Node { - """ - The ID of the object - """ - id: ID! - icon: String - body: UserNotificationBody! - seenState: UserNotificationSeenState! - kind: UserNotificationKind - createdAt: DateTime! +type DeleteNamespacePayload { + success: Boolean! + clientMutationId: String } -type UserNotificationBody { - text: String! - ranges: [NodeBodyRange]! +input DeleteNamespaceInput { + namespaceId: ID! + clientMutationId: String } -type UserNotificationConnection { - """ - Pagination data for this connection. - """ - pageInfo: PageInfo! +type InviteNamespaceCollaboratorPayload { + invite: NamespaceCollaboratorInvite! + namespace: Namespace! + clientMutationId: String +} - """ - Contains the nodes in this connection. - """ - edges: [UserNotificationEdge]! - hasPendingNotifications: Boolean! +input InviteNamespaceCollaboratorInput { + namespaceId: ID! + role: GrapheneRole! + username: String + email: String + clientMutationId: String } -type UserNotificationCreated { - notification: UserNotification - notificationDeletedId: ID +type AcceptNamespaceCollaboratorInvitePayload { + namespaceCollaboratorInvite: NamespaceCollaboratorInvite! + clientMutationId: String } -""" -A Relay edge containing a `UserNotification` and its cursor. -""" -type UserNotificationEdge { - """ - The item at the end of the edge - """ - node: UserNotification +input AcceptNamespaceCollaboratorInviteInput { + inviteId: ID! + clientMutationId: String +} - """ - A cursor for use in pagination - """ - cursor: String! +type RemoveNamespaceCollaboratorInvitePayload { + namespace: Namespace! + clientMutationId: String } -union UserNotificationKind = - UserNotificationKindPublishedPackageVersion - | UserNotificationKindIncomingPackageTransfer - | UserNotificationKindIncomingPackageInvite - | UserNotificationKindIncomingNamespaceInvite +input RemoveNamespaceCollaboratorInviteInput { + inviteId: ID! + clientMutationId: String +} -type UserNotificationKindIncomingNamespaceInvite { - namespaceInvite: NamespaceCollaboratorInvite! +type RemoveNamespaceCollaboratorPayload { + namespace: Namespace! + clientMutationId: String } -type UserNotificationKindIncomingPackageInvite { - packageInvite: PackageCollaboratorInvite! +input RemoveNamespaceCollaboratorInput { + namespaceCollaboratorId: ID! + clientMutationId: String } -type UserNotificationKindIncomingPackageTransfer { - packageTransferRequest: PackageTransferRequest! +type UpdateNamespaceCollaboratorRolePayload { + collaborator: NamespaceCollaborator! + clientMutationId: String } -type UserNotificationKindPublishedPackageVersion { - packageVersion: PackageVersion! +input UpdateNamespaceCollaboratorRoleInput { + namespaceCollaboratorId: ID! + role: GrapheneRole! + clientMutationId: String } -enum UserNotificationSeenState { - UNSEEN - SEEN - SEEN_AND_READ +type UpdateNamespaceCollaboratorInviteRolePayload { + collaboratorInvite: NamespaceCollaboratorInvite! + clientMutationId: String } -input ValidateUserEmailInput { - """ - The user id - """ - userId: ID - challenge: String! +input UpdateNamespaceCollaboratorInviteRoleInput { + namespaceCollaboratorInviteId: ID! + role: GrapheneRole! clientMutationId: String } -type ValidateUserEmailPayload { - user: User +type InvitePackageCollaboratorPayload { + invite: PackageCollaboratorInvite! + package: Package! clientMutationId: String } -input ValidateUserPasswordInput { - password: String! +input InvitePackageCollaboratorInput { + packageName: String! + role: GrapheneRole! + username: String + email: String clientMutationId: String } -type ValidateUserPasswordPayload { - success: Boolean +type AcceptPackageCollaboratorInvitePayload { + packageCollaboratorInvite: PackageCollaboratorInvite! clientMutationId: String } -type Verify { - payload: GenericScalar! +input AcceptPackageCollaboratorInviteInput { + inviteId: ID! + clientMutationId: String } -input WatchPackageInput { +type RemovePackageCollaboratorInvitePayload { + package: Package! + clientMutationId: String +} + +input RemovePackageCollaboratorInviteInput { + inviteId: ID! + clientMutationId: String +} + +type UpdatePackageCollaboratorRolePayload { + collaborator: PackageCollaborator! + clientMutationId: String +} + +input UpdatePackageCollaboratorRoleInput { + packageCollaboratorId: ID! + role: GrapheneRole! + clientMutationId: String +} + +type UpdatePackageCollaboratorInviteRolePayload { + collaboratorInvite: PackageCollaboratorInvite! + clientMutationId: String +} + +input UpdatePackageCollaboratorInviteRoleInput { + packageCollaboratorInviteId: ID! + role: GrapheneRole! + clientMutationId: String +} + +type RemovePackageCollaboratorPayload { + package: Package! + clientMutationId: String +} + +input RemovePackageCollaboratorInput { + packageCollaboratorId: ID! + clientMutationId: String +} + +type RequestPackageTransferPayload { + package: Package! + clientMutationId: String +} + +input RequestPackageTransferInput { packageId: ID! + newOwnerId: ID! clientMutationId: String } -type WatchPackagePayload { +type AcceptPackageTransferRequestPayload { + package: Package! + packageTransferRequest: PackageTransferRequest! + clientMutationId: String +} + +input AcceptPackageTransferRequestInput { + packageTransferRequestId: ID! + clientMutationId: String +} + +type RemovePackageTransferRequestPayload { package: Package! clientMutationId: String } + +input RemovePackageTransferRequestInput { + packageTransferRequestId: ID! + clientMutationId: String +} + +type GenerateBindingsForAllPackagesPayload { + message: String! + clientMutationId: String +} + +input GenerateBindingsForAllPackagesInput { + bindingsGeneratorId: ID + bindingsGeneratorCommand: String + clientMutationId: String +} + +type Subscription { + packageVersionCreated(publishedBy: ID, ownerId: ID): PackageVersion! + userNotificationCreated(userId: ID!): UserNotificationCreated! +} + +type UserNotificationCreated { + notification: UserNotification + notificationDeletedId: ID +} diff --git a/lib/registry/src/api.rs b/lib/registry/src/api.rs index 378c961ad01..db25b31cb9a 100644 --- a/lib/registry/src/api.rs +++ b/lib/registry/src/api.rs @@ -2,8 +2,8 @@ use anyhow::Context; use crate::RegistryClient; -use crate::graphql::mutations; -use crate::types::{PublishDeployAppOutput, PublishDeployAppRawVars}; +use crate::graphql::mutations::{self}; +use crate::types::{NewNonceOutput, PublishDeployAppOutput, PublishDeployAppRawVars}; /// Generate a Deploy token for for the given Deploy app version id. pub async fn generate_deploy_token( @@ -42,7 +42,9 @@ pub async fn publish_deploy_app_raw( .publish_deploy_app .context("Query did not return data")? .deploy_app_version; - let app = version.app.context("Query did not return expected data")?; + + let app = version.app; + // let app = version.app.context("Query did not return expected data")?; Ok(PublishDeployAppOutput { app_id: app.id, @@ -52,3 +54,26 @@ pub async fn publish_deploy_app_raw( owner_name: app.owner.global_name, }) } + +/// Generate a new Nonce +/// +/// Takes a name and a callbackUrl and returns a nonce +pub async fn get_nonce( + client: &RegistryClient, + name: String, + callback_url: String, +) -> Result { + let vars = mutations::new_nonce::Variables { name, callback_url }; + let nonce = client + .execute::(vars) + .await? + .new_nonce + .context("Query did not return a nonce")? + .nonce; + + Ok(NewNonceOutput { + id: nonce.id, + secret: nonce.secret, + auth_url: nonce.auth_url, + }) +} diff --git a/lib/registry/src/graphql/mutations.rs b/lib/registry/src/graphql/mutations.rs index b5611c98555..6bed4afb116 100644 --- a/lib/registry/src/graphql/mutations.rs +++ b/lib/registry/src/graphql/mutations.rs @@ -25,3 +25,11 @@ pub type JSONString = String; response_derives = "Debug" )] pub(crate) struct PublishDeployApp; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/mutations/new_nonce.graphql", + response_derives = "Debug" +)] +pub(crate) struct NewNonce; diff --git a/lib/registry/src/types.rs b/lib/registry/src/types.rs index 6e9f6b0d1dd..2defea30c2a 100644 --- a/lib/registry/src/types.rs +++ b/lib/registry/src/types.rs @@ -1,3 +1,5 @@ +use serde::Deserialize; + /// Payload for publishing a new Deploy app. #[derive(Clone, Debug)] pub struct PublishDeployAppRawVars { @@ -24,3 +26,18 @@ pub struct PublishDeployAppOutput { pub version_name: String, pub owner_name: String, } + +#[derive(Clone, Debug)] +pub struct NewNonceOutput { + pub id: String, + pub secret: String, + pub auth_url: String, +} + +/// Payload from the frontend after the user has authenticated. +/// +/// This has the token that we need to set in the WASMER_TOML file. +#[derive(Clone, Debug, Deserialize)] +pub struct ValidatedNonceOutput { + pub token: String, +} From 1ec4740be44de24e35aa7d347f072965f9ead06d Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Thu, 6 Jul 2023 12:55:06 +0100 Subject: [PATCH 02/25] implemented the top layer for logging in with a browser --- lib/cli/src/commands/login_with_browser.rs | 97 ++++++++++++++-------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/lib/cli/src/commands/login_with_browser.rs b/lib/cli/src/commands/login_with_browser.rs index dd517e37cad..06544f808f0 100644 --- a/lib/cli/src/commands/login_with_browser.rs +++ b/lib/cli/src/commands/login_with_browser.rs @@ -1,4 +1,4 @@ -use std::{net::TcpListener, path::PathBuf, sync::Arc, time::Duration}; +use std::{net::TcpListener, path::PathBuf, time::Duration}; use anyhow::Ok; @@ -15,9 +15,7 @@ use wasmer_registry::{ RegistryClient, }; -use axum::{http::StatusCode, routing::post, Json, Router}; - -use tokio::sync::{watch, RwLock}; +use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; const WASMER_CLI: &str = "wasmer-cli"; @@ -86,13 +84,6 @@ impl LoginWithBrowser { ) } - //As this function will only run once so return a Result - async fn save_validated_token(Json(payload): Json) -> StatusCode { - let ValidatedNonceOutput { token } = payload; - println!("Token: {}", token); - StatusCode::OK - } - async fn setup_listener() -> Result<(TcpListener, String), anyhow::Error> { let listener = TcpListener::bind("127.0.0.1:0")?; let addr = listener.local_addr()?; @@ -113,21 +104,28 @@ impl LoginWithBrowser { let registry = env.registry_endpoint()?; - let client = RegistryClient::new(registry, None, None); + let client = RegistryClient::new(registry.clone(), None, None); // let server_url = Self::setup_server().await?; let (listener, server_url) = Self::setup_listener().await?; let cors_middleware = CorsLayer::new() - .allow_headers([ - axum::http::header::AUTHORIZATION, - axum::http::header::CONTENT_TYPE, - ]) + .allow_headers([axum::http::header::CONTENT_TYPE]) .allow_methods([Method::POST]) .allow_origin(Any) .max_age(Duration::from_secs(60) * 10); - let app = Router::new().route("/", post(Self::save_validated_token)); + let (server_shutdown_tx, mut server_shutdown_rx) = tokio::sync::mpsc::channel::(1); + let (token_tx, mut token_rx) = tokio::sync::mpsc::channel::(1); + + // let state = ServerState { + // txs: Arc::new(Mutex::new((server_shutdown_tx, token_tx))), + // }; + + let app = Router::new().route( + "/", + post(save_validated_token).with_state((server_shutdown_tx.clone(), token_tx.clone())), + ); let app = app.layer(cors_middleware); let NewNonceOutput { auth_url, .. } = @@ -140,15 +138,15 @@ impl LoginWithBrowser { let auth_url = auth_url.split_once("cli").unwrap().1.to_string(); let auth_url = vercel_url + &auth_url; + // if failed to open the browser, then don't error out just print the auth_url with a message println!("Opening browser at {}", &auth_url); - opener::open_browser(&auth_url).map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to open browser at {} due to {}", &auth_url, e), - ) - })?; - - let (_tx, mut rx) = watch::channel((false, String::new())); + opener::open_browser(&auth_url).unwrap_or_else(|_| { + println!( + "Failed to open the browser.\n + Please open the url: {}", + &auth_url + ); + }); // let (tx, rx) = watch::channel((false, String::new())); // let shutdown_signal = Arc::new(RwLock::new(tx)); @@ -165,20 +163,53 @@ impl LoginWithBrowser { axum::Server::from_tcp(listener)? .serve(app.into_make_service()) - .with_graceful_shutdown(async move { rx.changed().await.unwrap() }) + .with_graceful_shutdown(async { + server_shutdown_rx.recv().await; + eprintln!("Shutting down server"); + }) .await?; - // match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { - // Some(s) => println!("Login for Wasmer user {:?} saved", s), - // None => println!( - // "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", - // registry, token - // ), - // } + // receive the token from the server + let token = token_rx + .recv() + .await + .expect("Failed to receive token from the server"); + + match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { + Some(s) => println!("Login for Wasmer user {:?} saved", s), + None => println!( + "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", + registry, token + ), + } Ok(()) } } +//As this function will only run once so return a Result +async fn save_validated_token( + State((shutdown_server_tx, token_tx)): State<( + tokio::sync::mpsc::Sender, + tokio::sync::mpsc::Sender, + )>, + Json(payload): Json, +) -> StatusCode { + let ValidatedNonceOutput { token } = payload; + println!("Token: {}", token); + + shutdown_server_tx + .send(true) + .await + .expect("Failed to send shutdown signal"); + + token_tx + .send(token.clone()) + .await + .expect("Failed to send token"); + + StatusCode::OK +} + // #[cfg(test)] // mod tests { // use clap::CommandFactory; From d10cadd56bf6aeb6807a11af3c7d35378a70cddb Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Thu, 6 Jul 2023 13:37:47 +0100 Subject: [PATCH 03/25] added browser functionality in main login command with --no-browser capabilites backwards compatibility --- lib/cli/src/cli.rs | 7 +- lib/cli/src/commands.rs | 5 +- lib/cli/src/commands/login.rs | 132 +++++++++- lib/cli/src/commands/login_with_browser.rs | 287 --------------------- 4 files changed, 130 insertions(+), 301 deletions(-) delete mode 100644 lib/cli/src/commands/login_with_browser.rs diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index ae623c274ac..7ed4dfabe34 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -9,8 +9,7 @@ use crate::commands::CreateExe; #[cfg(feature = "wast")] use crate::commands::Wast; use crate::commands::{ - Add, Cache, Config, Init, Inspect, Login, LoginWithBrowser, Publish, Run, SelfUpdate, Validate, - Whoami, + Add, Cache, Config, Init, Inspect, Login, Publish, Run, SelfUpdate, Validate, Whoami, }; #[cfg(feature = "static-artifact-create")] use crate::commands::{CreateObj, GenCHeader}; @@ -108,7 +107,6 @@ impl Args { Some(Cmd::Inspect(inspect)) => inspect.execute(), Some(Cmd::Init(init)) => init.execute(), Some(Cmd::Login(login)) => login.execute(), - Some(Cmd::LoginWithBrowser(login_with_browser)) => login_with_browser.execute(), Some(Cmd::Publish(publish)) => publish.execute(), #[cfg(feature = "static-artifact-create")] Some(Cmd::GenCHeader(gen_heder)) => gen_heder.execute(), @@ -139,9 +137,6 @@ enum Cmd { /// Login into a wasmer.io-like registry Login(Login), - #[clap(name = "login-with-browser")] - LoginWithBrowser(LoginWithBrowser), - /// Login into a wasmer.io-like registry #[clap(name = "publish")] Publish(Publish), diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index 4f03bb681a1..9f2365837cd 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -15,7 +15,6 @@ mod gen_c_header; mod init; mod inspect; mod login; -mod login_with_browser; mod publish; mod run; mod self_update; @@ -33,8 +32,8 @@ pub use create_exe::*; #[cfg(feature = "wast")] pub use wast::*; pub use { - add::*, cache::*, config::*, init::*, inspect::*, login::*, login_with_browser::*, publish::*, - run::Run, self_update::*, validate::*, whoami::*, + add::*, cache::*, config::*, init::*, inspect::*, login::*, publish::*, run::Run, + self_update::*, validate::*, whoami::*, }; #[cfg(feature = "static-artifact-create")] pub use {create_obj::*, gen_c_header::*}; diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index fa7158a358a..b14a4210b43 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -1,14 +1,30 @@ -use std::path::PathBuf; +use std::{net::TcpListener, path::PathBuf, time::Duration}; + +use anyhow::Ok; use clap::Parser; #[cfg(not(test))] use dialoguer::Input; +use reqwest::Method; +use tower_http::cors::{Any, CorsLayer}; + +use wasmer_registry::{ + types::NewNonceOutput, + types::ValidatedNonceOutput, + wasmer_env::{Registry, WasmerEnv, WASMER_DIR}, + RegistryClient, +}; -use wasmer_registry::wasmer_env::{Registry, WasmerEnv, WASMER_DIR}; +use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; -/// Subcommand for listing packages +const WASMER_CLI: &str = "wasmer-cli"; + +/// Subcommand for logging in using a browser #[derive(Debug, Clone, Parser)] pub struct Login { + /// Variable to login without opening a browser + #[clap(long, name = "no-browser")] + pub no_browser: bool, // Note: This is essentially a copy of WasmerEnv except the token is // accepted as a main argument instead of via --token. /// Set Wasmer's home directory @@ -41,6 +57,7 @@ impl Login { format!("Invalid registry for login {}: {e}", registry_host), ) })?; + let login_prompt = match ( registry_tld.domain.as_deref(), registry_tld.suffix.as_deref(), @@ -61,6 +78,65 @@ impl Login { } } + async fn get_token_from_browser(&self, env: &WasmerEnv) -> Result { + let registry = env.registry_endpoint()?; + + let client = RegistryClient::new(registry.clone(), None, None); + + let (listener, server_url) = Self::setup_listener().await?; + + let cors_middleware = CorsLayer::new() + .allow_headers([axum::http::header::CONTENT_TYPE]) + .allow_methods([Method::POST]) + .allow_origin(Any) + .max_age(Duration::from_secs(60) * 10); + + let (server_shutdown_tx, mut server_shutdown_rx) = tokio::sync::mpsc::channel::(1); + let (token_tx, mut token_rx) = tokio::sync::mpsc::channel::(1); + + let app = Router::new().route( + "/", + post(save_validated_token).with_state((server_shutdown_tx.clone(), token_tx.clone())), + ); + let app = app.layer(cors_middleware); + + let NewNonceOutput { auth_url, .. } = + wasmer_registry::api::get_nonce(&client, WASMER_CLI.to_string(), server_url).await?; + + // currently replace the auth_url with vercel's dev url + // https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli + + let vercel_url="https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli".to_string(); + let auth_url = auth_url.split_once("cli").unwrap().1.to_string(); + let auth_url = vercel_url + &auth_url; + + // if failed to open the browser, then don't error out just print the auth_url with a message + println!("Opening browser at {}", &auth_url); + opener::open_browser(&auth_url).unwrap_or_else(|_| { + println!( + "Failed to open the browser.\n + Please open the url: {}", + &auth_url + ); + }); + + // start the server + axum::Server::from_tcp(listener)? + .serve(app.into_make_service()) + .with_graceful_shutdown(async { + server_shutdown_rx.recv().await; + eprintln!("Shutting down server"); + }) + .await?; + + // receive the token from the server + let token = token_rx + .recv() + .await + .ok_or_else(|| anyhow::anyhow!("Failed to receive token from server"))?; + + Ok(token) + } fn wasmer_env(&self) -> WasmerEnv { WasmerEnv::new( self.wasmer_dir.clone(), @@ -70,12 +146,32 @@ impl Login { ) } + async fn setup_listener() -> Result<(TcpListener, String), anyhow::Error> { + let listener = TcpListener::bind("127.0.0.1:0")?; + let addr = listener.local_addr()?; + let port = addr.port(); + + let server_url = format!("http://localhost:{}", port); + + eprintln!("Server URL: {}", server_url); + + Ok((listener, server_url)) + } + /// execute [List] - pub fn execute(&self) -> Result<(), anyhow::Error> { + #[tokio::main] + pub async fn execute(&self) -> Result<(), anyhow::Error> { let env = self.wasmer_env(); - let token = self.get_token_or_ask_user(&env)?; + // let token = self.get_token_or_ask_user(&env)?; let registry = env.registry_endpoint()?; + + let token = if self.no_browser { + self.get_token_or_ask_user(&env)? + } else { + self.get_token_from_browser(&env).await? + }; + match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { Some(s) => println!("Login for Wasmer user {:?} saved", s), None => println!( @@ -87,6 +183,30 @@ impl Login { } } +//As this function will only run once so return a Result +async fn save_validated_token( + State((shutdown_server_tx, token_tx)): State<( + tokio::sync::mpsc::Sender, + tokio::sync::mpsc::Sender, + )>, + Json(payload): Json, +) -> StatusCode { + let ValidatedNonceOutput { token } = payload; + println!("Token: {}", token); + + shutdown_server_tx + .send(true) + .await + .expect("Failed to send shutdown signal"); + + token_tx + .send(token.clone()) + .await + .expect("Failed to send token"); + + StatusCode::OK +} + #[cfg(test)] mod tests { use clap::CommandFactory; @@ -98,6 +218,7 @@ mod tests { fn interactive_login() { let temp = TempDir::new().unwrap(); let login = Login { + no_browser: true, registry: Some("wasmer.wtf".into()), wasmer_dir: temp.path().to_path_buf(), token: None, @@ -117,6 +238,7 @@ mod tests { fn login_with_token() { let temp = TempDir::new().unwrap(); let login = Login { + no_browser: true, registry: Some("wasmer.wtf".into()), wasmer_dir: temp.path().to_path_buf(), token: Some("abc".to_string()), diff --git a/lib/cli/src/commands/login_with_browser.rs b/lib/cli/src/commands/login_with_browser.rs deleted file mode 100644 index 06544f808f0..00000000000 --- a/lib/cli/src/commands/login_with_browser.rs +++ /dev/null @@ -1,287 +0,0 @@ -use std::{net::TcpListener, path::PathBuf, time::Duration}; - -use anyhow::Ok; - -use clap::Parser; -#[cfg(not(test))] -use dialoguer::Input; -use reqwest::Method; -use tower_http::cors::{Any, CorsLayer}; - -use wasmer_registry::{ - types::NewNonceOutput, - types::ValidatedNonceOutput, - wasmer_env::{Registry, WasmerEnv, WASMER_DIR}, - RegistryClient, -}; - -use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; - -const WASMER_CLI: &str = "wasmer-cli"; - -/// Subcommand for logging in using a browser -#[derive(Debug, Clone, Parser)] -pub struct LoginWithBrowser { - // Note: This is essentially a copy of WasmerEnv except the token is - // accepted as a main argument instead of via --token. - /// Set Wasmer's home directory - #[clap(long, env = "WASMER_DIR", default_value = WASMER_DIR.as_os_str())] - pub wasmer_dir: PathBuf, - /// The registry to fetch packages from (inferred from the environment by - /// default) - #[clap(long, env = "WASMER_REGISTRY")] - pub registry: Option, - /// The API token to use when communicating with the registry (inferred from - /// the environment by default) - pub token: Option, - /// The directory cached artefacts are saved to. - #[clap(long, env = "WASMER_CACHE_DIR")] - cache_dir: Option, -} - -impl LoginWithBrowser { - fn _get_token_or_ask_user(&self, env: &WasmerEnv) -> Result { - if let Some(token) = &self.token { - return Ok(token.clone()); - } - - let registry_host = env.registry_endpoint()?; - let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default()) - .extract(registry_host.as_str()) - .map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Invalid registry for login {}: {e}", registry_host), - ) - })?; - - let login_prompt = match ( - registry_tld.domain.as_deref(), - registry_tld.suffix.as_deref(), - ) { - (Some(d), Some(s)) => { - format!("Please paste the login token from https://{d}.{s}/settings/access-tokens") - } - _ => "Please paste the login token".to_string(), - }; - #[cfg(test)] - { - Ok(login_prompt) - } - #[cfg(not(test))] - { - let token = Input::new().with_prompt(&login_prompt).interact_text()?; - Ok(token) - } - } - - fn wasmer_env(&self) -> WasmerEnv { - WasmerEnv::new( - self.wasmer_dir.clone(), - self.registry.clone(), - self.token.clone(), - self.cache_dir.clone(), - ) - } - - async fn setup_listener() -> Result<(TcpListener, String), anyhow::Error> { - let listener = TcpListener::bind("127.0.0.1:0")?; - let addr = listener.local_addr()?; - let port = addr.port(); - - let server_url = format!("http://localhost:{}", port); - - eprintln!("Server URL: {}", server_url); - - Ok((listener, server_url)) - } - - /// execute [List] - #[tokio::main] - pub async fn execute(&self) -> Result<(), anyhow::Error> { - let env = self.wasmer_env(); - // let token = self.get_token_or_ask_user(&env)?; - - let registry = env.registry_endpoint()?; - - let client = RegistryClient::new(registry.clone(), None, None); - - // let server_url = Self::setup_server().await?; - let (listener, server_url) = Self::setup_listener().await?; - - let cors_middleware = CorsLayer::new() - .allow_headers([axum::http::header::CONTENT_TYPE]) - .allow_methods([Method::POST]) - .allow_origin(Any) - .max_age(Duration::from_secs(60) * 10); - - let (server_shutdown_tx, mut server_shutdown_rx) = tokio::sync::mpsc::channel::(1); - let (token_tx, mut token_rx) = tokio::sync::mpsc::channel::(1); - - // let state = ServerState { - // txs: Arc::new(Mutex::new((server_shutdown_tx, token_tx))), - // }; - - let app = Router::new().route( - "/", - post(save_validated_token).with_state((server_shutdown_tx.clone(), token_tx.clone())), - ); - let app = app.layer(cors_middleware); - - let NewNonceOutput { auth_url, .. } = - wasmer_registry::api::get_nonce(&client, WASMER_CLI.to_string(), server_url).await?; - - // currently replace the auth_url with vercel's dev url - // https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli - - let vercel_url="https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli".to_string(); - let auth_url = auth_url.split_once("cli").unwrap().1.to_string(); - let auth_url = vercel_url + &auth_url; - - // if failed to open the browser, then don't error out just print the auth_url with a message - println!("Opening browser at {}", &auth_url); - opener::open_browser(&auth_url).unwrap_or_else(|_| { - println!( - "Failed to open the browser.\n - Please open the url: {}", - &auth_url - ); - }); - - // let (tx, rx) = watch::channel((false, String::new())); - // let shutdown_signal = Arc::new(RwLock::new(tx)); - - // let app = Router::new().route( - // "/", - // post(move |Json(payload): Json| { - // let ValidatedNonceOutput { token } = payload; - // let mut shutdown_signal = shutdown_signal.write().unwrap(); - // *shutdown_signal = (true, token.clone()); - // StatusCode::OK - // }), - // ); - - axum::Server::from_tcp(listener)? - .serve(app.into_make_service()) - .with_graceful_shutdown(async { - server_shutdown_rx.recv().await; - eprintln!("Shutting down server"); - }) - .await?; - - // receive the token from the server - let token = token_rx - .recv() - .await - .expect("Failed to receive token from the server"); - - match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { - Some(s) => println!("Login for Wasmer user {:?} saved", s), - None => println!( - "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", - registry, token - ), - } - Ok(()) - } -} - -//As this function will only run once so return a Result -async fn save_validated_token( - State((shutdown_server_tx, token_tx)): State<( - tokio::sync::mpsc::Sender, - tokio::sync::mpsc::Sender, - )>, - Json(payload): Json, -) -> StatusCode { - let ValidatedNonceOutput { token } = payload; - println!("Token: {}", token); - - shutdown_server_tx - .send(true) - .await - .expect("Failed to send shutdown signal"); - - token_tx - .send(token.clone()) - .await - .expect("Failed to send token"); - - StatusCode::OK -} - -// #[cfg(test)] -// mod tests { -// use clap::CommandFactory; -// use tempfile::TempDir; - -// use super::*; - -// #[test] -// fn interactive_login() { -// let temp = TempDir::new().unwrap(); -// let login = Login { -// registry: Some("wasmer.wtf".into()), -// wasmer_dir: temp.path().to_path_buf(), -// token: None, -// cache_dir: None, -// }; -// let env = login.wasmer_env(); - -// let token = login.get_token_or_ask_user(&env).unwrap(); - -// assert_eq!( -// token, -// "Please paste the login token from https://wasmer.wtf/settings/access-tokens" -// ); -// } - -// #[test] -// fn login_with_token() { -// let temp = TempDir::new().unwrap(); -// let login = Login { -// registry: Some("wasmer.wtf".into()), -// wasmer_dir: temp.path().to_path_buf(), -// token: Some("abc".to_string()), -// cache_dir: None, -// }; -// let env = login.wasmer_env(); - -// let token = login.get_token_or_ask_user(&env).unwrap(); - -// assert_eq!(token, "abc"); -// } - -// #[test] -// fn in_sync_with_wasmer_env() { -// let wasmer_env = WasmerEnv::command(); -// let login = Login::command(); - -// // All options except --token should be the same -// let wasmer_env_opts: Vec<_> = wasmer_env -// .get_opts() -// .filter(|arg| arg.get_id() != "token") -// .collect(); -// let login_opts: Vec<_> = login.get_opts().collect(); - -// assert_eq!(wasmer_env_opts, login_opts); - -// // The token argument should have the same message, even if it is an -// // argument rather than a --flag. -// let wasmer_env_token_help = wasmer_env -// .get_opts() -// .find(|arg| arg.get_id() == "token") -// .unwrap() -// .get_help() -// .unwrap() -// .to_string(); -// let login_token_help = login -// .get_positionals() -// .find(|arg| arg.get_id() == "token") -// .unwrap() -// .get_help() -// .unwrap() -// .to_string(); -// assert_eq!(wasmer_env_token_help, login_token_help); -// } -// } From 6ab96a859cb74dd674db1d6fe414bb603e41f062 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Thu, 6 Jul 2023 17:12:10 +0100 Subject: [PATCH 04/25] made prompting for `do you want to open in browser` --- lib/cli/src/commands/login.rs | 75 ++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index b14a4210b43..2954a1f84a5 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -1,10 +1,9 @@ -use std::{net::TcpListener, path::PathBuf, time::Duration}; +use std::{net::TcpListener, path::PathBuf, str::FromStr, time::Duration}; use anyhow::Ok; use clap::Parser; -#[cfg(not(test))] -use dialoguer::Input; +use dialoguer::{console::style, Input}; use reqwest::Method; use tower_http::cors::{Any, CorsLayer}; @@ -19,11 +18,42 @@ use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; const WASMER_CLI: &str = "wasmer-cli"; +/// Enum for the login prompt options +// Login using a browser - shows like `y/n` +#[derive(Debug, Clone, PartialEq)] +pub enum LoginBrowserPromptOptions { + /// Login with a browser - using `y/Y` + LoginUsingBrowser, + /// Login without a browser - using `n/N` + LoginWithoutBrowser, +} + +impl FromStr for LoginBrowserPromptOptions { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "y" | "Y" => Ok(LoginBrowserPromptOptions::LoginUsingBrowser), + "n" | "N" => Ok(LoginBrowserPromptOptions::LoginWithoutBrowser), + _ => Err(anyhow::anyhow!("Invalid option")), + } + } +} + +impl ToString for LoginBrowserPromptOptions { + fn to_string(&self) -> String { + match self { + LoginBrowserPromptOptions::LoginUsingBrowser => "y".to_string(), + LoginBrowserPromptOptions::LoginWithoutBrowser => "n".to_string(), + } + } +} + /// Subcommand for logging in using a browser #[derive(Debug, Clone, Parser)] pub struct Login { /// Variable to login without opening a browser - #[clap(long, name = "no-browser")] + #[clap(long, name = "no-browser", default_value = "false")] pub no_browser: bool, // Note: This is essentially a copy of WasmerEnv except the token is // accepted as a main argument instead of via --token. @@ -114,18 +144,19 @@ impl Login { println!("Opening browser at {}", &auth_url); opener::open_browser(&auth_url).unwrap_or_else(|_| { println!( - "Failed to open the browser.\n + "āš ļø Failed to open the browser.\n Please open the url: {}", &auth_url ); }); + println!("\n\nWaiting for the token from the browser...\n"); + // start the server axum::Server::from_tcp(listener)? .serve(app.into_make_service()) .with_graceful_shutdown(async { server_shutdown_rx.recv().await; - eprintln!("Shutting down server"); }) .await?; @@ -133,7 +164,7 @@ impl Login { let token = token_rx .recv() .await - .ok_or_else(|| anyhow::anyhow!("Failed to receive token from server"))?; + .ok_or_else(|| anyhow::anyhow!("āŒ Failed to receive token from server"))?; Ok(token) } @@ -166,10 +197,32 @@ impl Login { let registry = env.registry_endpoint()?; - let token = if self.no_browser { - self.get_token_or_ask_user(&env)? - } else { - self.get_token_from_browser(&env).await? + let token = match self.token.clone() { + Some(token) => token, + None => { + if self.no_browser { + self.get_token_or_ask_user(&env)? + } else { + // Ask the user to choose if he wants to login with the browser or not + let login_with_browser = Input::::new() + .with_prompt(&format!( + "{} {} [{}/n]", + style("?").yellow().bold(), + style("Do you want to login with the browser šŸŒ ?") + .bright() + .bold(), + style("y").green().bold() + )) + .show_default(false) + .default(LoginBrowserPromptOptions::LoginUsingBrowser) + .interact()?; + if login_with_browser == LoginBrowserPromptOptions::LoginUsingBrowser { + self.get_token_from_browser(&env).await? + } else { + self.get_token_or_ask_user(&env)? + } + } + } }; match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { From 1facf8d9a4153ad51bf040e1afdd765bdbf95023 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 12:05:35 +0100 Subject: [PATCH 05/25] happy with the flow --- lib/cli/src/commands/login.rs | 84 ++++++++++++++++------------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 2954a1f84a5..f90ed84117f 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -18,33 +18,32 @@ use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; const WASMER_CLI: &str = "wasmer-cli"; -/// Enum for the login prompt options -// Login using a browser - shows like `y/n` +/// Enum for the boolean like prompt options #[derive(Debug, Clone, PartialEq)] -pub enum LoginBrowserPromptOptions { - /// Login with a browser - using `y/Y` - LoginUsingBrowser, - /// Login without a browser - using `n/N` - LoginWithoutBrowser, +pub enum BoolPromptOptions { + /// Signifying a yes/true - using `y/Y` + Yes, + /// Signifying a No/false - using `n/N` + No, } -impl FromStr for LoginBrowserPromptOptions { +impl FromStr for BoolPromptOptions { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { - "y" | "Y" => Ok(LoginBrowserPromptOptions::LoginUsingBrowser), - "n" | "N" => Ok(LoginBrowserPromptOptions::LoginWithoutBrowser), + "y" | "Y" => Ok(BoolPromptOptions::Yes), + "n" | "N" => Ok(BoolPromptOptions::No), _ => Err(anyhow::anyhow!("Invalid option")), } } } -impl ToString for LoginBrowserPromptOptions { +impl ToString for BoolPromptOptions { fn to_string(&self) -> String { match self { - LoginBrowserPromptOptions::LoginUsingBrowser => "y".to_string(), - LoginBrowserPromptOptions::LoginWithoutBrowser => "n".to_string(), + BoolPromptOptions::Yes => "y".to_string(), + BoolPromptOptions::No => "n".to_string(), } } } @@ -133,13 +132,6 @@ impl Login { let NewNonceOutput { auth_url, .. } = wasmer_registry::api::get_nonce(&client, WASMER_CLI.to_string(), server_url).await?; - // currently replace the auth_url with vercel's dev url - // https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli - - let vercel_url="https://frontend-git-867-add-auth-flow-for-the-wasmer-cli-frontend-wapm.vercel.app/auth/cli".to_string(); - let auth_url = auth_url.split_once("cli").unwrap().1.to_string(); - let auth_url = vercel_url + &auth_url; - // if failed to open the browser, then don't error out just print the auth_url with a message println!("Opening browser at {}", &auth_url); opener::open_browser(&auth_url).unwrap_or_else(|_| { @@ -150,7 +142,7 @@ impl Login { ); }); - println!("\n\nWaiting for the token from the browser...\n"); + println!("\n\nWaiting for the token from the browser šŸŒ ... \n"); // start the server axum::Server::from_tcp(listener)? @@ -164,7 +156,7 @@ impl Login { let token = token_rx .recv() .await - .ok_or_else(|| anyhow::anyhow!("āŒ Failed to receive token from server"))?; + .ok_or_else(|| anyhow::anyhow!("āŒ Failed to receive token from localhost"))?; Ok(token) } @@ -193,36 +185,36 @@ impl Login { #[tokio::main] pub async fn execute(&self) -> Result<(), anyhow::Error> { let env = self.wasmer_env(); - // let token = self.get_token_or_ask_user(&env)?; - let registry = env.registry_endpoint()?; - let token = match self.token.clone() { - Some(token) => token, - None => { - if self.no_browser { - self.get_token_or_ask_user(&env)? - } else { - // Ask the user to choose if he wants to login with the browser or not - let login_with_browser = Input::::new() - .with_prompt(&format!( - "{} {} [{}/n]", + let person_wants_to_login = + match wasmer_registry::whoami(env.dir(), Some(registry.as_str()), None) { + std::result::Result::Ok((registry, user)) => { + println!("You are logged in as {:?} on registry {:?}", user, registry); + let login_again = Input::new() + .with_prompt(format!( + "{} {} - [y/{}]", style("?").yellow().bold(), - style("Do you want to login with the browser šŸŒ ?") - .bright() - .bold(), - style("y").green().bold() + style("Do you want to login again ?").bright().bold(), + style("N").green().bold() )) .show_default(false) - .default(LoginBrowserPromptOptions::LoginUsingBrowser) - .interact()?; - if login_with_browser == LoginBrowserPromptOptions::LoginUsingBrowser { - self.get_token_from_browser(&env).await? - } else { - self.get_token_or_ask_user(&env)? - } + .default(BoolPromptOptions::No) + .interact_text()?; + + login_again == BoolPromptOptions::Yes } - } + _ => true, + }; + + if !person_wants_to_login { + return Ok(()); + } + + let token = if self.no_browser { + self.get_token_or_ask_user(&env)? + } else { + self.get_token_from_browser(&env).await? }; match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { From edc451c96ec48644d39e62804a218f26cf967483 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 12:11:15 +0100 Subject: [PATCH 06/25] cosmetic changes to input prompt --- lib/cli/src/commands/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index f90ed84117f..bac66b3bcc1 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -195,7 +195,7 @@ impl Login { .with_prompt(format!( "{} {} - [y/{}]", style("?").yellow().bold(), - style("Do you want to login again ?").bright().bold(), + style("Do you want to login again?").bright().bold(), style("N").green().bold() )) .show_default(false) From 6ca884bdabd90215f6b2b78a0b98db443280d1fc Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 12:12:46 +0100 Subject: [PATCH 07/25] cosmetic changes for log prompt --- lib/cli/src/commands/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index bac66b3bcc1..a82c124aa27 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -218,7 +218,7 @@ impl Login { }; match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { - Some(s) => println!("Login for Wasmer user {:?} saved", s), + Some(s) => println!("āœ… Login for Wasmer user {:?} saved", s), None => println!( "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", registry, token From 49fd92b2e547292e901347e6c0519d44442372b3 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 12:29:11 +0100 Subject: [PATCH 08/25] ayush's reviewed changes --- lib/cli/src/commands/login.rs | 42 +++++++++++-------- .../graphql/mutations/new_nonce.graphql | 2 - lib/registry/src/api.rs | 4 +- lib/registry/src/types.rs | 2 - 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index a82c124aa27..af88e26f4d2 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -3,6 +3,7 @@ use std::{net::TcpListener, path::PathBuf, str::FromStr, time::Duration}; use anyhow::Ok; use clap::Parser; +#[cfg(not(test))] use dialoguer::{console::style, Input}; use reqwest::Method; use tower_http::cors::{Any, CorsLayer}; @@ -129,8 +130,8 @@ impl Login { ); let app = app.layer(cors_middleware); - let NewNonceOutput { auth_url, .. } = - wasmer_registry::api::get_nonce(&client, WASMER_CLI.to_string(), server_url).await?; + let NewNonceOutput { auth_url } = + wasmer_registry::api::create_nonce(&client, WASMER_CLI.to_string(), server_url).await?; // if failed to open the browser, then don't error out just print the auth_url with a message println!("Opening browser at {}", &auth_url); @@ -142,7 +143,7 @@ impl Login { ); }); - println!("\n\nWaiting for the token from the browser šŸŒ ... \n"); + println!("\n\nWaiting for authorization from the browser šŸŒ ... \n"); // start the server axum::Server::from_tcp(listener)? @@ -176,8 +177,6 @@ impl Login { let server_url = format!("http://localhost:{}", port); - eprintln!("Server URL: {}", server_url); - Ok((listener, server_url)) } @@ -191,18 +190,26 @@ impl Login { match wasmer_registry::whoami(env.dir(), Some(registry.as_str()), None) { std::result::Result::Ok((registry, user)) => { println!("You are logged in as {:?} on registry {:?}", user, registry); - let login_again = Input::new() - .with_prompt(format!( - "{} {} - [y/{}]", - style("?").yellow().bold(), - style("Do you want to login again?").bright().bold(), - style("N").green().bold() - )) - .show_default(false) - .default(BoolPromptOptions::No) - .interact_text()?; - - login_again == BoolPromptOptions::Yes + + #[cfg(not(test))] + { + let login_again = Input::new() + .with_prompt(format!( + "{} {} - [y/{}]", + style("?").yellow().bold(), + style("Do you want to login again?").bright().bold(), + style("N").green().bold() + )) + .show_default(false) + .default(BoolPromptOptions::No) + .interact_text()?; + + login_again == BoolPromptOptions::Yes + } + #[cfg(test)] + { + false + } } _ => true, }; @@ -237,7 +244,6 @@ async fn save_validated_token( Json(payload): Json, ) -> StatusCode { let ValidatedNonceOutput { token } = payload; - println!("Token: {}", token); shutdown_server_tx .send(true) diff --git a/lib/registry/graphql/mutations/new_nonce.graphql b/lib/registry/graphql/mutations/new_nonce.graphql index 5fb61cc8b2b..b5a6b1827e8 100644 --- a/lib/registry/graphql/mutations/new_nonce.graphql +++ b/lib/registry/graphql/mutations/new_nonce.graphql @@ -1,8 +1,6 @@ mutation NewNonce($name: String!, $callbackUrl: String!) { newNonce(input: { name: $name, callbackUrl: $callbackUrl }) { nonce { - id - secret authUrl } } diff --git a/lib/registry/src/api.rs b/lib/registry/src/api.rs index db25b31cb9a..5c107cea7d4 100644 --- a/lib/registry/src/api.rs +++ b/lib/registry/src/api.rs @@ -58,7 +58,7 @@ pub async fn publish_deploy_app_raw( /// Generate a new Nonce /// /// Takes a name and a callbackUrl and returns a nonce -pub async fn get_nonce( +pub async fn create_nonce( client: &RegistryClient, name: String, callback_url: String, @@ -72,8 +72,6 @@ pub async fn get_nonce( .nonce; Ok(NewNonceOutput { - id: nonce.id, - secret: nonce.secret, auth_url: nonce.auth_url, }) } diff --git a/lib/registry/src/types.rs b/lib/registry/src/types.rs index 2defea30c2a..aa843cd39cf 100644 --- a/lib/registry/src/types.rs +++ b/lib/registry/src/types.rs @@ -29,8 +29,6 @@ pub struct PublishDeployAppOutput { #[derive(Clone, Debug)] pub struct NewNonceOutput { - pub id: String, - pub secret: String, pub auth_url: String, } From 30b7e7536d9e5f4887c93260653866100bbcbaab Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 14:24:27 +0100 Subject: [PATCH 09/25] already logged in message changed --- lib/cli/src/commands/login.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index af88e26f4d2..fd9befb607c 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -189,7 +189,10 @@ impl Login { let person_wants_to_login = match wasmer_registry::whoami(env.dir(), Some(registry.as_str()), None) { std::result::Result::Ok((registry, user)) => { - println!("You are logged in as {:?} on registry {:?}", user, registry); + println!( + "You are already logged in as {:?} on registry {:?}", + user, registry + ); #[cfg(not(test))] { From 966c3f88b6f291082c4ce4fb952acff55f20936b Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 15:29:21 +0100 Subject: [PATCH 10/25] timeout implemented for 10 minutes --- lib/cli/src/commands/login.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index fd9befb607c..43d430787ca 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -134,7 +134,7 @@ impl Login { wasmer_registry::api::create_nonce(&client, WASMER_CLI.to_string(), server_url).await?; // if failed to open the browser, then don't error out just print the auth_url with a message - println!("Opening browser at {}", &auth_url); + println!("Opening auth link in your default browser: {}", &auth_url); opener::open_browser(&auth_url).unwrap_or_else(|_| { println!( "āš ļø Failed to open the browser.\n @@ -143,7 +143,7 @@ impl Login { ); }); - println!("\n\nWaiting for authorization from the browser šŸŒ ... \n"); + print!("Waiting for session... "); // start the server axum::Server::from_tcp(listener)? @@ -222,15 +222,33 @@ impl Login { } let token = if self.no_browser { - self.get_token_or_ask_user(&env)? + Some(self.get_token_or_ask_user(&env)?) } else { - self.get_token_from_browser(&env).await? + // switch between two methods of getting the token. + // start two async processes, 10 minute timeout and get token from browser. Whichever finishes first, use that. + + let timeout_future = tokio::time::sleep(Duration::from_secs(60) * 10); + + tokio::select! { + _ = timeout_future => { + print!("Timed out (10 mins exceeded)"); + None + }, + token = self.get_token_from_browser(&env) => { + Some(token?) + } + } }; + let token = token.ok_or_else(|| anyhow::anyhow!("Failed to get token"))?; + match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { - Some(s) => println!("āœ… Login for Wasmer user {:?} saved", s), + Some(s) => { + print!("Done!"); + println!("\nāœ… Login for Wasmer user {:?} saved", s) + } None => println!( - "Error: no user found on registry {:?} with token {:?}. Token saved regardless.", + "\nError: no user found on registry {:?} with token {:?}. Token saved regardless.", registry, token ), } From e09c043f357408966b1c949b1575720cf8505fd1 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 15:30:14 +0100 Subject: [PATCH 11/25] texts same as suggestions --- lib/cli/src/commands/login.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 43d430787ca..543f658088d 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -200,7 +200,9 @@ impl Login { .with_prompt(format!( "{} {} - [y/{}]", style("?").yellow().bold(), - style("Do you want to login again?").bright().bold(), + style("Do you want to login with another user?") + .bright() + .bold(), style("N").green().bold() )) .show_default(false) From f7d55b55c776d4eff59cdfe4a1c8947daa14b55c Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Fri, 7 Jul 2023 20:03:04 +0100 Subject: [PATCH 12/25] Authorization with all states, cancel, timeout and success --- lib/cli/src/commands/login.rs | 140 +++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 38 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 543f658088d..ef3b059c170 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -49,6 +49,15 @@ impl ToString for BoolPromptOptions { } } +type Token = String; + +#[derive(Debug, Clone)] +enum AuthorizationState { + TokenSuccess(Token), + Cancelled, + TimedOut, +} + /// Subcommand for logging in using a browser #[derive(Debug, Clone, Parser)] pub struct Login { @@ -73,9 +82,9 @@ pub struct Login { } impl Login { - fn get_token_or_ask_user(&self, env: &WasmerEnv) -> Result { + fn get_token_or_ask_user(&self, env: &WasmerEnv) -> Result { if let Some(token) = &self.token { - return Ok(token.clone()); + return Ok(AuthorizationState::TokenSuccess(token.clone())); } let registry_host = env.registry_endpoint()?; @@ -99,16 +108,19 @@ impl Login { }; #[cfg(test)] { - Ok(login_prompt) + Ok(AuthorizationState::TokenSuccess(login_prompt)) } #[cfg(not(test))] { let token = Input::new().with_prompt(&login_prompt).interact_text()?; - Ok(token) + Ok(AuthorizationState::TokenSuccess(token)) } } - async fn get_token_from_browser(&self, env: &WasmerEnv) -> Result { + async fn get_token_from_browser( + &self, + env: &WasmerEnv, + ) -> Result { let registry = env.registry_endpoint()?; let client = RegistryClient::new(registry.clone(), None, None); @@ -122,12 +134,16 @@ impl Login { .max_age(Duration::from_secs(60) * 10); let (server_shutdown_tx, mut server_shutdown_rx) = tokio::sync::mpsc::channel::(1); - let (token_tx, mut token_rx) = tokio::sync::mpsc::channel::(1); + let (token_tx, mut token_rx) = tokio::sync::mpsc::channel::(1); let app = Router::new().route( "/", - post(save_validated_token).with_state((server_shutdown_tx.clone(), token_tx.clone())), + post(authorize_token).with_state((server_shutdown_tx.clone(), token_tx.clone())), ); + // .route( + // "/cancel-login", + // get(cancel_login).with_state((server_shutdown_tx.clone(), token_tx.clone())), + // ); let app = app.layer(cors_middleware); let NewNonceOutput { auth_url } = @@ -161,6 +177,7 @@ impl Login { Ok(token) } + fn wasmer_env(&self) -> WasmerEnv { WasmerEnv::new( self.wasmer_dir.clone(), @@ -223,64 +240,98 @@ impl Login { return Ok(()); } - let token = if self.no_browser { - Some(self.get_token_or_ask_user(&env)?) + let auth_state = if self.no_browser { + self.get_token_or_ask_user(&env)? } else { // switch between two methods of getting the token. // start two async processes, 10 minute timeout and get token from browser. Whichever finishes first, use that. - let timeout_future = tokio::time::sleep(Duration::from_secs(60) * 10); - tokio::select! { _ = timeout_future => { - print!("Timed out (10 mins exceeded)"); - None + AuthorizationState::TimedOut }, token = self.get_token_from_browser(&env) => { - Some(token?) + token? } } }; - let token = token.ok_or_else(|| anyhow::anyhow!("Failed to get token"))?; - - match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { - Some(s) => { - print!("Done!"); - println!("\nāœ… Login for Wasmer user {:?} saved", s) + match auth_state { + AuthorizationState::TokenSuccess(token) => { + match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { + Some(s) => { + print!("Done!"); + println!("\nāœ… Login for Wasmer user {:?} saved", s) + } + None => println!( + "\nError: no user found on registry {:?} with token {:?}. Token saved regardless.", + registry, token + ), + }; } - None => println!( - "\nError: no user found on registry {:?} with token {:?}. Token saved regardless.", - registry, token - ), - } + AuthorizationState::TimedOut => { + print!("Timed out (10 mins exceeded)"); + } + AuthorizationState::Cancelled => { + print!("Cancelled by the user"); + } + }; Ok(()) } } //As this function will only run once so return a Result -async fn save_validated_token( +async fn authorize_token( State((shutdown_server_tx, token_tx)): State<( tokio::sync::mpsc::Sender, - tokio::sync::mpsc::Sender, + tokio::sync::mpsc::Sender, )>, Json(payload): Json, ) -> StatusCode { let ValidatedNonceOutput { token } = payload; + match token.is_empty() { + true => { + token_tx + .send(AuthorizationState::Cancelled) + .await + .expect("Failed to send token"); + } + false => { + token_tx + .send(AuthorizationState::TokenSuccess(token.clone())) + .await + .expect("Failed to send token"); + } + } + shutdown_server_tx .send(true) .await .expect("Failed to send shutdown signal"); - token_tx - .send(token.clone()) - .await - .expect("Failed to send token"); - StatusCode::OK } +// async fn cancel_login( +// State((shutdown_server_tx, token_tx)): State<( +// tokio::sync::mpsc::Sender, +// tokio::sync::mpsc::Sender, +// )>, +// ) -> &'static str { +// shutdown_server_tx +// .send(true) +// .await +// .expect("Failed to send shutdown signal"); + +// token_tx +// .send(AuthorizationState::Cancelled) +// .await +// .expect("Failed to send token"); + +// "Login cancelled" +// } + #[cfg(test)] mod tests { use clap::CommandFactory; @@ -301,11 +352,17 @@ mod tests { let env = login.wasmer_env(); let token = login.get_token_or_ask_user(&env).unwrap(); - - assert_eq!( - token, - "Please paste the login token from https://wasmer.wtf/settings/access-tokens" - ); + match token { + AuthorizationState::TokenSuccess(token) => { + assert_eq!( + token, + "Please paste the login token from https://wasmer.wtf/settings/access-tokens" + ); + } + AuthorizationState::Cancelled | AuthorizationState::TimedOut => { + panic!("Should not reach here") + } + } } #[test] @@ -322,7 +379,14 @@ mod tests { let token = login.get_token_or_ask_user(&env).unwrap(); - assert_eq!(token, "abc"); + match token { + AuthorizationState::TokenSuccess(token) => { + assert_eq!(token, "abc"); + } + AuthorizationState::Cancelled | AuthorizationState::TimedOut => { + panic!("Should not reach here") + } + } } #[test] From ebc99cd7547bd8693f0192d757603b1ef5acd074 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Sat, 8 Jul 2023 09:37:43 +0100 Subject: [PATCH 13/25] http server switched to barebones hyper --- Cargo.lock | 69 +--------------- lib/cli/Cargo.toml | 61 ++++++++++---- lib/cli/src/commands/login.rs | 148 ++++++++++++++++++++++------------ 3 files changed, 145 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ef4a8aeb1c..844a84ce97b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,55 +254,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "axum" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" -dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.67" @@ -2018,9 +1969,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -2510,12 +2461,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" -[[package]] -name = "matchit" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" - [[package]] name = "md5" version = "0.7.0" @@ -4384,12 +4329,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "tap" version = "1.0.1" @@ -5688,7 +5627,6 @@ dependencies = [ "assert_cmd 2.0.11", "async-trait", "atty", - "axum", "bytes", "bytesize", "cargo_metadata", @@ -5701,6 +5639,7 @@ dependencies = [ "distance", "flate2", "hex", + "hyper", "indexmap", "indicatif", "isatty", @@ -5727,8 +5666,6 @@ dependencies = [ "tldextract", "tokio", "toml 0.5.11", - "tower", - "tower-http", "tracing", "tracing-subscriber 0.3.17", "unix_mode", diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index a0231c8d037..e3689541966 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -28,21 +28,41 @@ required-features = ["headless"] [dependencies] # Repo-local dependencies. wasmer = { version = "=4.0.0", path = "../api", default-features = false } -wasmer-compiler = { version = "=4.0.0", path = "../compiler", features = ["compiler"], optional = true } +wasmer-compiler = { version = "=4.0.0", path = "../compiler", features = [ + "compiler", +], optional = true } wasmer-compiler-cranelift = { version = "=4.0.0", path = "../compiler-cranelift", optional = true } wasmer-compiler-singlepass = { version = "=4.0.0", path = "../compiler-singlepass", optional = true } wasmer-compiler-llvm = { version = "=4.0.0", path = "../compiler-llvm", optional = true } wasmer-emscripten = { version = "=4.0.0", path = "../emscripten" } wasmer-vm = { version = "=4.0.0", path = "../vm", optional = true } -wasmer-wasix = { version = "0.9.0", path = "../wasix", features = ["logging", "webc_runner", "webc_runner_rt_wcgi", "webc_runner_rt_wasi", "webc_runner_rt_emscripten", "host-fs"] } -wasmer-wasix-experimental-io-devices = { version = "0.9.0", path = "../wasi-experimental-io-devices", optional = true, features = ["link_external_libs"] } +wasmer-wasix = { version = "0.9.0", path = "../wasix", features = [ + "logging", + "webc_runner", + "webc_runner_rt_wcgi", + "webc_runner_rt_wasi", + "webc_runner_rt_emscripten", + "host-fs", +] } +wasmer-wasix-experimental-io-devices = { version = "0.9.0", path = "../wasi-experimental-io-devices", optional = true, features = [ + "link_external_libs", +] } wasmer-wast = { version = "=4.0.0", path = "../../tests/lib/wast", optional = true } -wasmer-cache = { version = "=4.0.0", path = "../cache", features = ["blake3-pure"] } -wasmer-types = { version = "=4.0.0", path = "../types", features = ["enable-serde"] } -wasmer-registry = { version = "5.2.0", path = "../registry", features = ["build-package", "clap"] } +wasmer-cache = { version = "=4.0.0", path = "../cache", features = [ + "blake3-pure", +] } +wasmer-types = { version = "=4.0.0", path = "../types", features = [ + "enable-serde", +] } +wasmer-registry = { version = "5.2.0", path = "../registry", features = [ + "build-package", + "clap", +] } wasmer-object = { version = "=4.0.0", path = "../object", optional = true } -virtual-fs = { version = "0.6.0", path = "../virtual-fs", default-features = false, features = ["host-fs"] } -virtual-net = { version = "0.3.0", path = "../virtual-net" } +virtual-fs = { version = "0.6.0", path = "../virtual-fs", default-features = false, features = [ + "host-fs", +] } +virtual-net = { version = "0.3.0", path = "../virtual-net" } # Wasmer-owned dependencies. webc = { workspace = true } @@ -90,15 +110,13 @@ sha2 = "0.10.6" object = "0.30.0" wasm-coredump-builder = { version = "0.1.11", optional = true } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", features = [ "env-filter", "fmt" ] } +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } async-trait = "0.1.68" tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] } once_cell = "1.17.1" indicatif = "0.17.5" -axum = "0.6.18" -tower = "0.4.13" -tower-http = "0.4.1" opener = "0.6.1" +hyper = { version = "0.14.27", features = ["full"] } # NOTE: Must use different features for clap because the "color" feature does not # work on wasi due to the anstream dependency not compiling. @@ -113,16 +131,27 @@ clap = { version = "4.2.7", default-features = false, features = [ "suggestions", "derive", "env", -]} +] } [target.'cfg(not(target_arch = "riscv64"))'.dependencies] -reqwest = { version = "^0.11", default-features = false, features = ["rustls-tls", "json", "multipart"] } +reqwest = { version = "^0.11", default-features = false, features = [ + "rustls-tls", + "json", + "multipart", +] } [target.'cfg(target_arch = "riscv64")'.dependencies] -reqwest = { version = "^0.11", default-features = false, features = ["native-tls", "json", "multipart"] } +reqwest = { version = "^0.11", default-features = false, features = [ + "native-tls", + "json", + "multipart", +] } [build-dependencies] -chrono = { version = "^0.4", default-features = false, features = ["std", "clock"] } +chrono = { version = "^0.4", default-features = false, features = [ + "std", + "clock", +] } [target.'cfg(target_os = "linux")'.dependencies] unix_mode = "0.1.3" diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index ef3b059c170..553e3f18bcf 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -6,7 +6,6 @@ use clap::Parser; #[cfg(not(test))] use dialoguer::{console::style, Input}; use reqwest::Method; -use tower_http::cors::{Any, CorsLayer}; use wasmer_registry::{ types::NewNonceOutput, @@ -15,7 +14,10 @@ use wasmer_registry::{ RegistryClient, }; -use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; +use hyper::{ + service::{make_service_fn, service_fn}, + Body, Request, Response, Server, StatusCode, +}; const WASMER_CLI: &str = "wasmer-cli"; @@ -56,6 +58,13 @@ enum AuthorizationState { TokenSuccess(Token), Cancelled, TimedOut, + UnknownMethod, +} + +#[derive(Clone)] +struct AppContext { + server_shutdown_tx: tokio::sync::mpsc::Sender, + token_tx: tokio::sync::mpsc::Sender, } /// Subcommand for logging in using a browser @@ -127,24 +136,14 @@ impl Login { let (listener, server_url) = Self::setup_listener().await?; - let cors_middleware = CorsLayer::new() - .allow_headers([axum::http::header::CONTENT_TYPE]) - .allow_methods([Method::POST]) - .allow_origin(Any) - .max_age(Duration::from_secs(60) * 10); - let (server_shutdown_tx, mut server_shutdown_rx) = tokio::sync::mpsc::channel::(1); let (token_tx, mut token_rx) = tokio::sync::mpsc::channel::(1); - let app = Router::new().route( - "/", - post(authorize_token).with_state((server_shutdown_tx.clone(), token_tx.clone())), - ); - // .route( - // "/cancel-login", - // get(cancel_login).with_state((server_shutdown_tx.clone(), token_tx.clone())), - // ); - let app = app.layer(cors_middleware); + // Create a new AppContext + let app_context = AppContext { + server_shutdown_tx, + token_tx, + }; let NewNonceOutput { auth_url } = wasmer_registry::api::create_nonce(&client, WASMER_CLI.to_string(), server_url).await?; @@ -154,16 +153,27 @@ impl Login { opener::open_browser(&auth_url).unwrap_or_else(|_| { println!( "āš ļø Failed to open the browser.\n - Please open the url: {}", + Please open the url: {}", &auth_url ); }); + // Create a new server + let make_svc = make_service_fn(move |_| { + let context = app_context.clone(); + + // Create a `Service` for responding to the request. + let service = service_fn(move |req| service_router(context.clone(), req)); + + // Return the service to hyper. + async move { Ok(service) } + }); + print!("Waiting for session... "); // start the server - axum::Server::from_tcp(listener)? - .serve(app.into_make_service()) + Server::from_tcp(listener)? + .serve(make_svc) .with_graceful_shutdown(async { server_shutdown_rx.recv().await; }) @@ -275,20 +285,37 @@ impl Login { AuthorizationState::Cancelled => { print!("Cancelled by the user"); } + AuthorizationState::UnknownMethod => { + print!("Error: unknown method"); + } }; Ok(()) } } -//As this function will only run once so return a Result -async fn authorize_token( - State((shutdown_server_tx, token_tx)): State<( - tokio::sync::mpsc::Sender, - tokio::sync::mpsc::Sender, - )>, - Json(payload): Json, -) -> StatusCode { - let ValidatedNonceOutput { token } = payload; +async fn preflight(req: Request) -> Result, anyhow::Error> { + let _whole_body = hyper::body::aggregate(req).await?; + let response = Response::builder() + .status(StatusCode::OK) + .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary + .header("Access-Control-Allow-Headers", "Content-Type") + .header("Access-Control-Allow-Methods", "POST, OPTIONS") + .body(Body::default())?; + Ok(response) +} + +async fn handle_post_save_token( + context: AppContext, + req: Request, +) -> Result, anyhow::Error> { + let AppContext { + server_shutdown_tx, + token_tx, + } = context; + let (.., body) = req.into_parts(); + let body = hyper::body::to_bytes(body).await?; + + let token = serde_json::from_slice::(&body)?.token; match token.is_empty() { true => { @@ -305,32 +332,47 @@ async fn authorize_token( } } - shutdown_server_tx + server_shutdown_tx + .send(true) + .await + .expect("Failed to send shutdown signal"); + + Ok(Response::new(Body::from("Auth token received"))) +} + +async fn handle_unknown_method(context: AppContext) -> Result, anyhow::Error> { + let AppContext { + server_shutdown_tx, + token_tx, + } = context; + + token_tx + .send(AuthorizationState::UnknownMethod) + .await + .expect("Failed to send token"); + + server_shutdown_tx .send(true) .await .expect("Failed to send shutdown signal"); - StatusCode::OK + Ok(Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::from("Method not allowed"))?) } -// async fn cancel_login( -// State((shutdown_server_tx, token_tx)): State<( -// tokio::sync::mpsc::Sender, -// tokio::sync::mpsc::Sender, -// )>, -// ) -> &'static str { -// shutdown_server_tx -// .send(true) -// .await -// .expect("Failed to send shutdown signal"); - -// token_tx -// .send(AuthorizationState::Cancelled) -// .await -// .expect("Failed to send token"); - -// "Login cancelled" -// } +/// Handle the preflight headers first - OPTIONS request +/// Then proceed to handle the actual request - POST request +async fn service_router( + context: AppContext, + req: Request, +) -> Result, anyhow::Error> { + match *req.method() { + Method::OPTIONS => preflight(req).await, + Method::POST => handle_post_save_token(context, req).await, + _ => handle_unknown_method(context).await, + } +} #[cfg(test)] mod tests { @@ -359,7 +401,9 @@ mod tests { "Please paste the login token from https://wasmer.wtf/settings/access-tokens" ); } - AuthorizationState::Cancelled | AuthorizationState::TimedOut => { + AuthorizationState::Cancelled + | AuthorizationState::TimedOut + | AuthorizationState::UnknownMethod => { panic!("Should not reach here") } } @@ -383,7 +427,9 @@ mod tests { AuthorizationState::TokenSuccess(token) => { assert_eq!(token, "abc"); } - AuthorizationState::Cancelled | AuthorizationState::TimedOut => { + AuthorizationState::Cancelled + | AuthorizationState::TimedOut + | AuthorizationState::UnknownMethod => { panic!("Should not reach here") } } From 612fabd790e706d4edb97fe58f930fe9f67dc2bc Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Sat, 8 Jul 2023 15:40:05 +0100 Subject: [PATCH 14/25] ValidatedNonceOutput TokenStatus added --- lib/cli/src/commands/login.rs | 27 ++++++++++++++++++++------- lib/registry/src/types.rs | 25 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 553e3f18bcf..411a3e5a2e1 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -315,29 +315,42 @@ async fn handle_post_save_token( let (.., body) = req.into_parts(); let body = hyper::body::to_bytes(body).await?; - let token = serde_json::from_slice::(&body)?.token; - - match token.is_empty() { - true => { + let ValidatedNonceOutput { + token, + status: token_status, + } = serde_json::from_slice::(&body)?; + + // send the AuthorizationState based on token_status to the main thread and get the response message + let response_message = match token_status { + wasmer_registry::types::TokenStatus::Cancelled => { token_tx .send(AuthorizationState::Cancelled) .await .expect("Failed to send token"); + + "Token Cancelled by the user" } - false => { + wasmer_registry::types::TokenStatus::Authorized => { token_tx .send(AuthorizationState::TokenSuccess(token.clone())) .await .expect("Failed to send token"); + + "Token Authorized" } - } + }; server_shutdown_tx .send(true) .await .expect("Failed to send shutdown signal"); - Ok(Response::new(Body::from("Auth token received"))) + Ok(Response::builder() + .status(StatusCode::OK) + .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary + .header("Access-Control-Allow-Headers", "Content-Type") + .header("Access-Control-Allow-Methods", "POST, OPTIONS") + .body(Body::from(response_message))?) } async fn handle_unknown_method(context: AppContext) -> Result, anyhow::Error> { diff --git a/lib/registry/src/types.rs b/lib/registry/src/types.rs index aa843cd39cf..4951ea057eb 100644 --- a/lib/registry/src/types.rs +++ b/lib/registry/src/types.rs @@ -32,10 +32,35 @@ pub struct NewNonceOutput { pub auth_url: String, } +/// Payload from the frontend after the user has authenticated. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TokenStatus { + Cancelled, + Authorized, +} + +fn token_status_deserializer<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + match s.as_str() { + "cancelled" => Ok(TokenStatus::Cancelled), + "authorized" => Ok(TokenStatus::Authorized), + _ => Err(serde::de::Error::custom(format!( + "invalid token status: {}", + s + ))), + } +} + /// Payload from the frontend after the user has authenticated. /// /// This has the token that we need to set in the WASMER_TOML file. #[derive(Clone, Debug, Deserialize)] pub struct ValidatedNonceOutput { pub token: String, + #[serde(deserialize_with = "token_status_deserializer")] + pub status: TokenStatus, } From ff13040d6774f765950056c9a158f0d0f5554c1b Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Sat, 8 Jul 2023 15:50:03 +0100 Subject: [PATCH 15/25] added quotes in login message --- lib/cli/src/commands/login.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 411a3e5a2e1..52faef322ed 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -271,10 +271,10 @@ impl Login { match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { Some(s) => { print!("Done!"); - println!("\nāœ… Login for Wasmer user {:?} saved", s) + println!("\nāœ… Login for Wasmer user \"{:?}\" saved", s) } None => println!( - "\nError: no user found on registry {:?} with token {:?}. Token saved regardless.", + "\nError: no user found on registry \"{:?}\" with token \"{:?}\". Token saved regardless.", registry, token ), }; From 70d676e2d78ba57478ef14be9721d57e17727501 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Sun, 9 Jul 2023 20:02:03 +0100 Subject: [PATCH 16/25] moved types to login --- lib/cli/Cargo.toml | 60 +++++++++++++++++++++++++++++------ lib/cli/src/commands/login.rs | 58 +++++++++++++++++++++++++-------- lib/registry/src/types.rs | 35 -------------------- 3 files changed, 95 insertions(+), 58 deletions(-) diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index e3689541966..dcb32779873 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -116,7 +116,7 @@ tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] } once_cell = "1.17.1" indicatif = "0.17.5" opener = "0.6.1" -hyper = { version = "0.14.27", features = ["full"] } +hyper = { version = "0.14.27", features = ["server"] } # NOTE: Must use different features for clap because the "color" feature does not # work on wasi due to the anstream dependency not compiling. @@ -159,7 +159,14 @@ unix_mode = "0.1.3" [features] # Don't add the compiler features in default, please add them on the Makefile # since we might want to autoconfigure them depending on the availability on the host. -default = ["sys", "wat", "wast", "compiler", "wasmer-artifact-create", "static-artifact-create"] +default = [ + "sys", + "wat", + "wast", + "compiler", + "wasmer-artifact-create", + "static-artifact-create", +] backend = [] coredump = ["wasm-coredump-builder"] sys = ["compiler", "wasmer-vm"] @@ -167,21 +174,56 @@ jsc = ["backend", "wasmer/jsc", "wasmer/std"] wast = ["wasmer-wast"] host-net = ["virtual-net/host-net"] wat = ["wasmer/wat"] -compiler = ["backend", "wasmer/compiler", "wasmer-compiler/translator", "wasmer-compiler/compiler"] -wasmer-artifact-create = ["compiler", "wasmer/wasmer-artifact-load", "wasmer/wasmer-artifact-create", "wasmer-compiler/wasmer-artifact-load", "wasmer-compiler/wasmer-artifact-create", "wasmer-object"] -static-artifact-create = ["compiler", "wasmer/static-artifact-load", "wasmer/static-artifact-create", "wasmer-compiler/static-artifact-load", "wasmer-compiler/static-artifact-create", "wasmer-object"] -wasmer-artifact-load = ["compiler", "wasmer/wasmer-artifact-load", "wasmer-compiler/wasmer-artifact-load"] -static-artifact-load = ["compiler", "wasmer/static-artifact-load", "wasmer-compiler/static-artifact-load"] +compiler = [ + "backend", + "wasmer/compiler", + "wasmer-compiler/translator", + "wasmer-compiler/compiler", +] +wasmer-artifact-create = [ + "compiler", + "wasmer/wasmer-artifact-load", + "wasmer/wasmer-artifact-create", + "wasmer-compiler/wasmer-artifact-load", + "wasmer-compiler/wasmer-artifact-create", + "wasmer-object", +] +static-artifact-create = [ + "compiler", + "wasmer/static-artifact-load", + "wasmer/static-artifact-create", + "wasmer-compiler/static-artifact-load", + "wasmer-compiler/static-artifact-create", + "wasmer-object", +] +wasmer-artifact-load = [ + "compiler", + "wasmer/wasmer-artifact-load", + "wasmer-compiler/wasmer-artifact-load", +] +static-artifact-load = [ + "compiler", + "wasmer/static-artifact-load", + "wasmer-compiler/static-artifact-load", +] experimental-io-devices = ["wasmer-wasix-experimental-io-devices"] singlepass = ["wasmer-compiler-singlepass", "compiler"] cranelift = ["wasmer-compiler-cranelift", "compiler"] llvm = ["wasmer-compiler-llvm", "compiler"] -disable-all-logging = ["wasmer-wasix/disable-all-logging", "log/release_max_level_off"] +disable-all-logging = [ + "wasmer-wasix/disable-all-logging", + "log/release_max_level_off", +] headless = [] headless-minimal = ["headless", "disable-all-logging"] # Optional -enable-serde = ["wasmer/enable-serde", "wasmer-vm/enable-serde", "wasmer-compiler/enable-serde", "wasmer-wasix/enable-serde"] +enable-serde = [ + "wasmer/enable-serde", + "wasmer-vm/enable-serde", + "wasmer-compiler/enable-serde", + "wasmer-wasix/enable-serde", +] [dev-dependencies] assert_cmd = "2.0.11" diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 52faef322ed..ee788f4e6c7 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -7,9 +7,9 @@ use clap::Parser; use dialoguer::{console::style, Input}; use reqwest::Method; +use serde::Deserialize; use wasmer_registry::{ types::NewNonceOutput, - types::ValidatedNonceOutput, wasmer_env::{Registry, WasmerEnv, WASMER_DIR}, RegistryClient, }; @@ -21,6 +21,27 @@ use hyper::{ const WASMER_CLI: &str = "wasmer-cli"; +/// Payload from the frontend after the user has authenticated. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TokenStatus { + /// Signifying that the token is cancelled + Cancelled, + /// Signifying that the token is authorized + Authorized, +} + +/// Payload from the frontend after the user has authenticated. +/// +/// This has the token that we need to set in the WASMER_TOML file. +#[derive(Clone, Debug, Deserialize)] +pub struct ValidatedNonceOutput { + /// Token Received from the frontend + pub token: Option, + /// Status of the token , whether it is authorized or cancelled + pub status: TokenStatus, +} + /// Enum for the boolean like prompt options #[derive(Debug, Clone, PartialEq)] pub enum BoolPromptOptions { @@ -271,10 +292,10 @@ impl Login { match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { Some(s) => { print!("Done!"); - println!("\nāœ… Login for Wasmer user \"{:?}\" saved", s) + println!("\nāœ… Login for Wasmer user {:?} saved", s) } None => println!( - "\nError: no user found on registry \"{:?}\" with token \"{:?}\". Token saved regardless.", + "\nError: no user found on registry {:?} with token \"{:?}\". Token saved regardless.", registry, token ), }; @@ -321,22 +342,25 @@ async fn handle_post_save_token( } = serde_json::from_slice::(&body)?; // send the AuthorizationState based on token_status to the main thread and get the response message - let response_message = match token_status { - wasmer_registry::types::TokenStatus::Cancelled => { + let (response_message, parse_failure) = match token_status { + TokenStatus::Cancelled => { token_tx .send(AuthorizationState::Cancelled) .await .expect("Failed to send token"); - "Token Cancelled by the user" + ("Token Cancelled by the user", false) } - wasmer_registry::types::TokenStatus::Authorized => { - token_tx - .send(AuthorizationState::TokenSuccess(token.clone())) - .await - .expect("Failed to send token"); - - "Token Authorized" + TokenStatus::Authorized => { + if let Some(token) = token { + token_tx + .send(AuthorizationState::TokenSuccess(token.clone())) + .await + .expect("Failed to send token"); + ("Token Authorized", false) + } else { + ("Token not found", true) + } } }; @@ -345,8 +369,14 @@ async fn handle_post_save_token( .await .expect("Failed to send shutdown signal"); + let status = if parse_failure { + StatusCode::BAD_REQUEST + } else { + StatusCode::OK + }; + Ok(Response::builder() - .status(StatusCode::OK) + .status(status) .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary .header("Access-Control-Allow-Headers", "Content-Type") .header("Access-Control-Allow-Methods", "POST, OPTIONS") diff --git a/lib/registry/src/types.rs b/lib/registry/src/types.rs index 4951ea057eb..3db9a2bf8c5 100644 --- a/lib/registry/src/types.rs +++ b/lib/registry/src/types.rs @@ -1,5 +1,3 @@ -use serde::Deserialize; - /// Payload for publishing a new Deploy app. #[derive(Clone, Debug)] pub struct PublishDeployAppRawVars { @@ -31,36 +29,3 @@ pub struct PublishDeployAppOutput { pub struct NewNonceOutput { pub auth_url: String, } - -/// Payload from the frontend after the user has authenticated. -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum TokenStatus { - Cancelled, - Authorized, -} - -fn token_status_deserializer<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - match s.as_str() { - "cancelled" => Ok(TokenStatus::Cancelled), - "authorized" => Ok(TokenStatus::Authorized), - _ => Err(serde::de::Error::custom(format!( - "invalid token status: {}", - s - ))), - } -} - -/// Payload from the frontend after the user has authenticated. -/// -/// This has the token that we need to set in the WASMER_TOML file. -#[derive(Clone, Debug, Deserialize)] -pub struct ValidatedNonceOutput { - pub token: String, - #[serde(deserialize_with = "token_status_deserializer")] - pub status: TokenStatus, -} From e64411dd1635e643fa6eabc89b8ab5cf7e6870fd Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 10:05:49 +0100 Subject: [PATCH 17/25] case where user pastes a token --- lib/cli/src/commands/login.rs | 101 +++++++++++++++++----------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index ee788f4e6c7..449cb02d173 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -234,58 +234,61 @@ impl Login { let env = self.wasmer_env(); let registry = env.registry_endpoint()?; - let person_wants_to_login = - match wasmer_registry::whoami(env.dir(), Some(registry.as_str()), None) { - std::result::Result::Ok((registry, user)) => { - println!( - "You are already logged in as {:?} on registry {:?}", - user, registry - ); - - #[cfg(not(test))] - { - let login_again = Input::new() - .with_prompt(format!( - "{} {} - [y/{}]", - style("?").yellow().bold(), - style("Do you want to login with another user?") - .bright() - .bold(), - style("N").green().bold() - )) - .show_default(false) - .default(BoolPromptOptions::No) - .interact_text()?; - - login_again == BoolPromptOptions::Yes - } - #[cfg(test)] - { - false + let auth_state = match self.token.clone() { + Some(token) => Ok(AuthorizationState::TokenSuccess(token)), + None => { + let person_wants_to_login = + match wasmer_registry::whoami(env.dir(), Some(registry.as_str()), None) { + std::result::Result::Ok((registry, user)) => { + println!( + "You are already logged in as {:?} on registry {:?}", + user, registry + ); + + #[cfg(not(test))] + { + let login_again = Input::new() + .with_prompt(format!( + "{} {} - [y/{}]", + style("?").yellow().bold(), + style("Do you want to login with another user?") + .bright() + .bold(), + style("N").green().bold() + )) + .show_default(false) + .default(BoolPromptOptions::No) + .interact_text()?; + + login_again == BoolPromptOptions::Yes + } + #[cfg(test)] + { + false + } + } + _ => true, + }; + + if !person_wants_to_login { + Ok(AuthorizationState::Cancelled) + } else if self.no_browser { + self.get_token_or_ask_user(&env) + } else { + // switch between two methods of getting the token. + // start two async processes, 10 minute timeout and get token from browser. Whichever finishes first, use that. + let timeout_future = tokio::time::sleep(Duration::from_secs(60) * 10); + tokio::select! { + _ = timeout_future => { + Ok(AuthorizationState::TimedOut) + }, + token = self.get_token_from_browser(&env) => { + token + } } } - _ => true, - }; - - if !person_wants_to_login { - return Ok(()); - } - - let auth_state = if self.no_browser { - self.get_token_or_ask_user(&env)? - } else { - // switch between two methods of getting the token. - // start two async processes, 10 minute timeout and get token from browser. Whichever finishes first, use that. - let timeout_future = tokio::time::sleep(Duration::from_secs(60) * 10); - tokio::select! { - _ = timeout_future => { - AuthorizationState::TimedOut - }, - token = self.get_token_from_browser(&env) => { - token? - } } - }; + }?; match auth_state { AuthorizationState::TokenSuccess(token) => { From 58bd9690c5a10961f562b0b2e7131ee4920c3cd2 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 10:07:45 +0100 Subject: [PATCH 18/25] doc suggestion --- lib/cli/src/commands/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 449cb02d173..859753f22cf 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -88,7 +88,7 @@ struct AppContext { token_tx: tokio::sync::mpsc::Sender, } -/// Subcommand for logging in using a browser +/// Subcommand for log in a user into Wasmer (using a browser or provided a token) #[derive(Debug, Clone, Parser)] pub struct Login { /// Variable to login without opening a browser From ba3f1e8eac2dc56399c545a67f4cdf4f5bf081de Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 10:10:48 +0100 Subject: [PATCH 19/25] new line after Cancelled --- lib/cli/src/commands/login.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 859753f22cf..38cdf10dfc2 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -307,10 +307,10 @@ impl Login { print!("Timed out (10 mins exceeded)"); } AuthorizationState::Cancelled => { - print!("Cancelled by the user"); + println!("Cancelled by the user\n"); } AuthorizationState::UnknownMethod => { - print!("Error: unknown method"); + println!("Error: unknown method\n"); } }; Ok(()) From d807e3f7942ccea5d177803751e13f70d3c97bee Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 10:44:50 +0100 Subject: [PATCH 20/25] multiply the numbers directly in timeout --- lib/cli/src/commands/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 38cdf10dfc2..a2c27999897 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -277,7 +277,7 @@ impl Login { } else { // switch between two methods of getting the token. // start two async processes, 10 minute timeout and get token from browser. Whichever finishes first, use that. - let timeout_future = tokio::time::sleep(Duration::from_secs(60) * 10); + let timeout_future = tokio::time::sleep(Duration::from_secs(60 * 10)); tokio::select! { _ = timeout_future => { Ok(AuthorizationState::TimedOut) From 15319fff96820ee71d3ab483ddb880dff2777246 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 15:21:17 +0100 Subject: [PATCH 21/25] Message formatting for wrong token error --- lib/cli/src/commands/login.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index a2c27999897..a6aad6885aa 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -292,14 +292,19 @@ impl Login { match auth_state { AuthorizationState::TokenSuccess(token) => { - match wasmer_registry::login::login_and_save_token(env.dir(), registry.as_str(), &token)? { + match wasmer_registry::login::login_and_save_token( + env.dir(), + registry.as_str(), + &token, + )? { Some(s) => { print!("Done!"); println!("\nāœ… Login for Wasmer user {:?} saved", s) } - None => println!( - "\nError: no user found on registry {:?} with token \"{:?}\". Token saved regardless.", - registry, token + None => print!( + "Error: no user found on {:?} with token {:?}.\nToken saved regardless.", + registry.domain().unwrap_or("registry.wasmer.io"), + token ), }; } @@ -307,7 +312,7 @@ impl Login { print!("Timed out (10 mins exceeded)"); } AuthorizationState::Cancelled => { - println!("Cancelled by the user\n"); + println!("Cancelled by the user"); } AuthorizationState::UnknownMethod => { println!("Error: unknown method\n"); From 85de7b45ec2bbcfd9a10c9d2b09d7fb896ca4d05 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 15:53:52 +0100 Subject: [PATCH 22/25] warning message exchanged with error for wrong token --- lib/cli/src/commands/login.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index a6aad6885aa..18f1b9bbecd 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -302,9 +302,8 @@ impl Login { println!("\nāœ… Login for Wasmer user {:?} saved", s) } None => print!( - "Error: no user found on {:?} with token {:?}.\nToken saved regardless.", - registry.domain().unwrap_or("registry.wasmer.io"), - token + "Warning: no user found on {:?} with the provided token. Token saved regardless.", + registry.domain().unwrap_or("registry.wasmer.io") ), }; } From 8c3feacfc9f07ade720b33d23323aeae951c315d Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Mon, 10 Jul 2023 15:54:50 +0100 Subject: [PATCH 23/25] newline after warning --- lib/cli/src/commands/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 18f1b9bbecd..27258c0ea77 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -302,7 +302,7 @@ impl Login { println!("\nāœ… Login for Wasmer user {:?} saved", s) } None => print!( - "Warning: no user found on {:?} with the provided token. Token saved regardless.", + "Warning: no user found on {:?} with the provided token.\nToken saved regardless.", registry.domain().unwrap_or("registry.wasmer.io") ), }; From 3eb3cf3800d11dfe351acb521fa325da23fb3a51 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Tue, 11 Jul 2023 13:29:15 +0100 Subject: [PATCH 24/25] ignore the pure_webc_package test --- tests/wasmer-web/tests/wasi-web.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wasmer-web/tests/wasi-web.rs b/tests/wasmer-web/tests/wasi-web.rs index 61a90aba5ee..10abf868cec 100644 --- a/tests/wasmer-web/tests/wasi-web.rs +++ b/tests/wasmer-web/tests/wasi-web.rs @@ -58,6 +58,7 @@ async fn run_a_webc_package_that_involves_the_filesystem(client: Client) { ); } +#[ignore] // FIXME: This test is flaky on CI - @Michael-F-Bryan #[macro_rules_attribute::apply(browser_test)] async fn pure_webc_package(client: Client) { client.wait_for_xterm(str::contains(PROMPT)).await; From 9d0027b7f228ad37f2dc59db876edf4579a139f5 Mon Sep 17 00:00:00 2001 From: Rudra Arora Date: Tue, 11 Jul 2023 15:41:42 +0100 Subject: [PATCH 25/25] login_works test updated --- tests/integration/cli/tests/login.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/cli/tests/login.rs b/tests/integration/cli/tests/login.rs index 7980f774cad..e06951fd5c8 100644 --- a/tests/integration/cli/tests/login.rs +++ b/tests/integration/cli/tests/login.rs @@ -15,6 +15,7 @@ fn login_works() -> anyhow::Result<()> { if wapm_dev_token.is_empty() { return Ok(()); } + // FIXME: Change the registry to wasmer.wtf and all the associated terms with WAPM let output = Command::new(get_wasmer_path()) .arg("login") .arg("--registry") @@ -37,7 +38,7 @@ fn login_works() -> anyhow::Result<()> { } let stdout_output = std::str::from_utf8(&output.stdout).unwrap(); - let expected = "Login for Wasmer user \"ciuser\" saved\n"; + let expected = "Done!\nāœ… Login for Wasmer user \"ciuser\" saved\n"; if stdout_output != expected { println!("expected:"); println!("{expected}");