Single sign-on servers need no setup step
With single sign-on there is no first-start code and no /setup form: the identity provider says who may sign in and who is admin, and the first allowed sign-in makes the organisation, named after ARCHIFY_SYNC_NAME. Server 0.1.1, chart 0.1.2.
This commit is contained in:
1 parent
d8d46266dc
commit
01a35f7ba6
7 files changed
+45
-12
No files matched your search
Generated
+3
-3
@@ -43,7 +43,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "archify-sync-protocol"
|
name = "archify-sync-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -53,7 +53,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "archify-sync-server"
|
name = "archify-sync-server"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
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.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
+1
-1
@@ -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.0"
|
version = "0.1.1"
|
||||||
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"
|
||||||
@@ -15,7 +15,7 @@ One Rust process and PostgreSQL. The design, with its decisions, is the Archify
|
|||||||
- **Restore:** brings an earlier version back as a new edit and a new version; history is never rewritten.
|
- **Restore:** brings an earlier version back as a new edit and a new version; history is never rewritten.
|
||||||
- **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.** 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.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
@@ -36,7 +36,7 @@ docker run -d --name archify-pg -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=archify
|
|||||||
ARCHIFY_SYNC_DATABASE_URL=postgres://postgres:dev@127.0.0.1:5432/archify cargo run -p archify-sync-server
|
ARCHIFY_SYNC_DATABASE_URL=postgres://postgres:dev@127.0.0.1:5432/archify cargo run -p archify-sync-server
|
||||||
```
|
```
|
||||||
|
|
||||||
The log shows the first-start code. Open `http://localhost:8080/setup` and enter it.
|
Without single sign-on, the log shows the first-start code. Open `http://localhost:8080/setup` and enter it.
|
||||||
|
|
||||||
Or the server and its database in containers:
|
Or the server and its database in containers:
|
||||||
|
|
||||||
|
|||||||
@@ -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.1
|
version: 0.1.2
|
||||||
appVersion: "0.1.0"
|
appVersion: "0.1.1"
|
||||||
annotations:
|
annotations:
|
||||||
artifacthub.io/license: MIT
|
artifacthub.io/license: MIT
|
||||||
sources:
|
sources:
|
||||||
|
|||||||
@@ -531,9 +531,12 @@ async fn sso_account(
|
|||||||
.invite_organisation(&auth::sha256(invite))
|
.invite_organisation(&auth::sha256(invite))
|
||||||
.await?
|
.await?
|
||||||
.map(|(org, _)| org),
|
.map(|(org, _)| org),
|
||||||
None if !config.allowed_groups.is_empty() || admin => {
|
None if !config.allowed_groups.is_empty() || admin => Some(
|
||||||
state.store.first_organisation().await?
|
state
|
||||||
}
|
.store
|
||||||
|
.organisation_or_create(&state.config.server_name)
|
||||||
|
.await?,
|
||||||
|
),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let Some(organisation) = organisation else {
|
let Some(organisation) = organisation else {
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ impl AppState {
|
|||||||
let secret = STANDARD.decode(stored.as_str().unwrap_or_default())?;
|
let secret = STANDARD.decode(stored.as_str().unwrap_or_default())?;
|
||||||
anyhow::ensure!(secret.len() >= 32, "the stored signing key is too short");
|
anyhow::ensure!(secret.len() >= 32, "the stored signing key is too short");
|
||||||
|
|
||||||
let first_start = if store.account_count().await? == 0 {
|
// With single sign-on the identity provider says who is admin; there
|
||||||
|
// is no first-start code and no setup page.
|
||||||
|
let first_start = if config.oidc.is_none() && store.account_count().await? == 0 {
|
||||||
let code = auth::first_start_code();
|
let code = auth::first_start_code();
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"first start: no accounts yet. Set up the first admin at {}/setup with code {code}",
|
"first start: no accounts yet. Set up the first admin at {}/setup with code {code}",
|
||||||
|
|||||||
@@ -313,6 +313,34 @@ impl Store {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The server's organisation, made with `name` when there is none yet
|
||||||
|
/// (a single sign-on server has no setup step).
|
||||||
|
pub async fn organisation_or_create(&self, name: &str) -> Result<Uuid> {
|
||||||
|
let mut tx = self.pool.begin().await?;
|
||||||
|
// Serialises the first sign-ins, so only one organisation is made.
|
||||||
|
sqlx::query("LOCK TABLE organisations IN EXCLUSIVE MODE")
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
let found: Option<Uuid> =
|
||||||
|
sqlx::query_scalar("SELECT id FROM organisations ORDER BY created_at LIMIT 1")
|
||||||
|
.fetch_optional(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
let id = match found {
|
||||||
|
Some(id) => id,
|
||||||
|
None => {
|
||||||
|
let id = Uuid::now_v7();
|
||||||
|
sqlx::query("INSERT INTO organisations (id, name) VALUES ($1, $2)")
|
||||||
|
.bind(id)
|
||||||
|
.bind(name)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tx.commit().await?;
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
/// The server's organisation: the one made at setup.
|
/// The server's organisation: the one made at setup.
|
||||||
pub async fn first_organisation(&self) -> Result<Option<Uuid>> {
|
pub async fn first_organisation(&self) -> Result<Option<Uuid>> {
|
||||||
Ok(
|
Ok(
|
||||||
|
|||||||
Reference in new issue
Block a user