diff --git a/Cargo.lock b/Cargo.lock index 9ce9727..2a32836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ dependencies = [ [[package]] name = "archify-sync-protocol" -version = "0.1.1" +version = "0.1.2" dependencies = [ "chrono", "serde", @@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "archify-sync-server" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "archify-sync-protocol", @@ -83,7 +83,7 @@ dependencies = [ [[package]] name = "archify-sync-store" -version = "0.1.1" +version = "0.1.2" dependencies = [ "chrono", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 93ba85d..38554af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["crates/protocol", "crates/store", "crates/server"] [workspace.package] -version = "0.1.1" +version = "0.1.2" edition = "2024" license = "MIT" repository = "https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync" diff --git a/README.md b/README.md index db1361a..0bcc0de 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ One Rust process and PostgreSQL. The design, with its decisions, is the Archify - **Update log:** taking a version compacts it. - **Sign-in.** The editor opens `/authorize` in the browser and gets a one-time code back through its URI handler. It exchanges the code with its PKCE verifier for an access token (JWT, 15 minutes) and a refresh token. The refresh token is single use: reusing one signs out the whole session. - **First start.** With single sign-on there is no setup step: the identity provider says who may sign in and who is admin, and the first sign-in makes the organisation (named after `ARCHIFY_SYNC_NAME`). Without single sign-on, a server without accounts prints a first-start code in its log; the first person to use it at `/setup` becomes the admin. -- **Invites.** An invite link (`/join/`) opens the extension. Someone new makes an account on the sign-in page, so no mail server is needed. +- **Invites.** An invite link (`/join/`) opens the extension. Someone new makes an account on the sign-in page, so no mail server is needed. With single sign-on, the identity provider is the only way in: the sign-in page has no password form, and an invited person signs in there. ## Layout diff --git a/charts/archify-sync/Chart.yaml b/charts/archify-sync/Chart.yaml index a667de4..cf2396d 100644 --- a/charts/archify-sync/Chart.yaml +++ b/charts/archify-sync/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: archify-sync description: Archify Sync, live and shared Archify models for VS Code type: application -version: 0.1.2 -appVersion: "0.1.1" +version: 0.1.3 +appVersion: "0.1.2" annotations: artifacthub.io/license: MIT sources: diff --git a/crates/server/src/pages.rs b/crates/server/src/pages.rs index 3166c87..7c58fc6 100644 --- a/crates/server/src/pages.rs +++ b/crates/server/src/pages.rs @@ -215,24 +215,29 @@ async fn authorize_form(state: &AppState, q: &AuthorizeQuery, error: Option<&str .unwrap_or_default(), ] .concat(); - // Single sign-on first when the server has it; the password form stays for local accounts. - let mut body = state - .oidc - .as_ref() - .map(|o| { - format!( - r#"

Sign in for VS Code

{}Sign in with {}
"#, - error_line(error), - escape(&q.to_query_string()), - escape(&o.config.label), - ) - }) - .unwrap_or_default(); - let (title, error) = if body.is_empty() { - ("Sign in for VS Code", error) - } else { - ("Or with a password", None) - }; + // With single sign-on the identity provider is the only way in: no + // password form, no new local accounts. An invite goes through it too. + if let Some(o) = &state.oidc { + let invited = match &q.invite { + Some(invite) => match state.store.invite_organisation(&auth::sha256(invite)).await { + Ok(Some((_, space_name))) => format!( + r#"

You were invited to {}.

"#, + escape(&space_name) + ), + _ => String::new(), + }, + None => String::new(), + }; + let body = format!( + r#"

Sign in for VS Code

{}{invited}Sign in with {}
"#, + error_line(error), + escape(&q.to_query_string()), + escape(&o.config.label), + ); + return page(state, "Sign in", &body); + } + let mut body = String::new(); + let title = "Sign in for VS Code"; body.push_str(&format!( r#"

{title}

{}
{fields}{} @@ -269,6 +274,10 @@ pub async fn authorize_submit( if check_authorize(&state, &q).is_err() { return authorize_form(&state, &q, None).await; } + if let Some(o) = &state.oidc { + let message = format!("This server signs in with {} only.", o.config.label); + return authorize_form(&state, &q, Some(&message)).await; + } let account = match form.action.as_str() { "signup" => sign_up(&state, &form).await, _ => sign_in(&state, &form).await,