Single sign-on is the only way in when it is configured

The sign-in page shows only the identity provider; password sign-in and
new local accounts are refused. Invites go through the identity provider.
Server 0.1.2, chart 0.1.3.
This commit is contained in:
root-at-skic committed 2026-11-08 19:39:02 +02:00
1 parent 6114a994db
commit 7c86198002
5 files changed
+34 -25

No files matched your search

Generated
+3 -3
View File
@@ -43,7 +43,7 @@ dependencies = [
[[package]] [[package]]
name = "archify-sync-protocol" name = "archify-sync-protocol"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",
@@ -53,7 +53,7 @@ dependencies = [
[[package]] [[package]]
name = "archify-sync-server" name = "archify-sync-server"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"archify-sync-protocol", "archify-sync-protocol",
@@ -83,7 +83,7 @@ dependencies = [
[[package]] [[package]]
name = "archify-sync-store" name = "archify-sync-store"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde_json", "serde_json",
+1 -1
View File
@@ -3,7 +3,7 @@ resolver = "3"
members = ["crates/protocol", "crates/store", "crates/server"] members = ["crates/protocol", "crates/store", "crates/server"]
[workspace.package] [workspace.package]
version = "0.1.1" version = "0.1.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync" repository = "https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync"
+1 -1
View File
@@ -16,7 +16,7 @@ One Rust process and PostgreSQL. The design, with its decisions, is the Archify
- **Update log:** taking a version compacts it. - **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. - **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. - **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/<token>`) opens the extension. Someone new makes an account on the sign-in page, so no mail server is needed. - **Invites.** An invite link (`/join/<token>`) 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 ## Layout
+2 -2
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: archify-sync name: archify-sync
description: Archify Sync, live and shared Archify models for VS Code description: Archify Sync, live and shared Archify models for VS Code
type: application type: application
version: 0.1.2 version: 0.1.3
appVersion: "0.1.1" appVersion: "0.1.2"
annotations: annotations:
artifacthub.io/license: MIT artifacthub.io/license: MIT
sources: sources:
+27 -18
View File
@@ -215,24 +215,29 @@ async fn authorize_form(state: &AppState, q: &AuthorizeQuery, error: Option<&str
.unwrap_or_default(), .unwrap_or_default(),
] ]
.concat(); .concat();
// Single sign-on first when the server has it; the password form stays for local accounts. // With single sign-on the identity provider is the only way in: no
let mut body = state // password form, no new local accounts. An invite goes through it too.
.oidc if let Some(o) = &state.oidc {
.as_ref() let invited = match &q.invite {
.map(|o| { Some(invite) => match state.store.invite_organisation(&auth::sha256(invite)).await {
format!( Ok(Some((_, space_name))) => format!(
r#"<div class="card"><h2>Sign in for VS Code</h2>{}<a class="button" href="/oidc/start?{}">Sign in with {}</a></div>"#, r#"<p class="muted">You were invited to <b>{}</b>.</p>"#,
error_line(error), escape(&space_name)
escape(&q.to_query_string()), ),
escape(&o.config.label), _ => String::new(),
) },
}) None => String::new(),
.unwrap_or_default(); };
let (title, error) = if body.is_empty() { let body = format!(
("Sign in for VS Code", error) r#"<div class="card"><h2>Sign in for VS Code</h2>{}{invited}<a class="button" href="/oidc/start?{}">Sign in with {}</a></div>"#,
} else { error_line(error),
("Or with a password", None) 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!( body.push_str(&format!(
r#"<div class="card"><h2>{title}</h2>{} r#"<div class="card"><h2>{title}</h2>{}
<form method="post" action="/authorize">{fields}{} <form method="post" action="/authorize">{fields}{}
@@ -269,6 +274,10 @@ pub async fn authorize_submit(
if check_authorize(&state, &q).is_err() { if check_authorize(&state, &q).is_err() {
return authorize_form(&state, &q, None).await; 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() { let account = match form.action.as_str() {
"signup" => sign_up(&state, &form).await, "signup" => sign_up(&state, &form).await,
_ => sign_in(&state, &form).await, _ => sign_in(&state, &form).await,