Skip to content

Commit

Permalink
Merge pull request #71 from sebadob/impl-rauthy-db-version-check
Browse files Browse the repository at this point in the history
DB version check fully working
  • Loading branch information
sebadob committed Oct 7, 2023
2 parents 43f5b61 + 058b6a8 commit d2d9271
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 8 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 9 additions & 8 deletions dev_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@

## CURRENT WORK

## TODO before v0.16.0

- add a new table that keeps track about when password expiry / reset emails were sent out to avoid duplicates
- update the book with all the new features

## Stage 1 - essentials

[x] finished

## Stage 2 - features - do before v1.0.0

- create a nice looking error page for things like expired magic link, bad client values, things like that...
- introduce 'rauthy-db-version' or something like that into the config table to be able to do stricter validation
between feature / major version migrations
- add a new table that keeps track about when password expiry / reset emails were sent out to avoid duplicates
- NATS events stream or maybe internal one?
- benchmarks and performance tuning
- add an 'ip blacklist' feature?
- add all default claims for users https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
- double check against https://openid.net/specs/openid-connect-core-1_0.html that everything is implemented correctly one more time
- benchmarks and performance tuning

## Stage 3 - Possible nice to haves

- add an 'ip blacklist' feature?
- auto-encrypted backups + backups to remote locations (ssh, nfs, s3, ...) -> postponed - should be applied to sqlite only
since postgres has pg_backrest and a lot of well established tooling anyway
- add all default claims for users https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
- oidc-client (google, github, ...)
- 'rauthy-migrate' project to help migrating to rauthy?
- custom event listener template to build own implementation?
- custom event listener template to build own implementation? -> only if NATS will be implemented maybe?
1 change: 1 addition & 0 deletions rauthy-models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ rauthy-common = { path = "../rauthy-common" }
redhac = "0.7"
regex = "1"
ring = "0.16"
semver = { version = "1.0.19", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { workspace = true }
Expand Down
13 changes: 13 additions & 0 deletions rauthy-models/src/app_state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::email::EMail;
use crate::entity::db_version::DbVersion;
use crate::migration::db_migrate;
use crate::migration::db_migrate::migrate_init_prod;
use crate::migration::db_migrate_dev::migrate_dev_data;
Expand Down Expand Up @@ -283,6 +284,13 @@ impl AppState {
pool
};

// before we do any db migrations, we need to check the current DB version for
// for compatibility
let db_version = DbVersion::check_app_version(&pool)
.await
.map_err(|err| anyhow::Error::msg(err.message))?;

// migrate DB data
if !*DEV_MODE {
migrate_init_prod(
&pool,
Expand Down Expand Up @@ -344,6 +352,11 @@ impl AppState {
error!("Error when applying anti-lockout check: {:?}", err);
}

// update the DbVersion after successful pool creation and migrations
DbVersion::update(&pool, db_version)
.await
.map_err(|err| anyhow::Error::msg(err.message))?;

Ok(pool)
}

Expand Down
135 changes: 135 additions & 0 deletions rauthy-models/src/entity/db_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::app_state::DbPool;
use rauthy_common::constants::RAUTHY_VERSION;
use rauthy_common::error_response::ErrorResponse;
use semver::Version;
use serde::{Deserialize, Serialize};
use sqlx::query;
use std::str::FromStr;
use tracing::{debug, warn};

#[derive(Debug, Serialize, Deserialize)]
pub struct DbVersion {
pub version: Version,
}

impl DbVersion {
pub async fn find(db: &DbPool) -> Option<Self> {
let res = query!("select data from config where id = 'db_version'")
.fetch_optional(db)
.await
.ok()?;
match res {
Some(res) => {
let data = res
.data
.expect("to get 'data' back from the AppVersion query");
bincode::deserialize::<Self>(&data).ok()
}
None => None,
}
}

pub async fn update(db: &DbPool, db_version: Option<Version>) -> Result<(), ErrorResponse> {
let app_version = Self::app_version();
if Some(&app_version) != db_version.as_ref() {
let slf = Self {
version: app_version,
};
let data = bincode::serialize(&slf)?;

#[cfg(feature = "sqlite")]
let q = query!(
"insert or replace into config (id, data) values ('db_version', $1)",
data,
);
#[cfg(not(feature = "sqlite"))]
let q = query!(
r#"insert into config (id, data) values ('db_version', $1)
on conflict(id) do update set data = $1"#,
data,
);
q.execute(db).await?;
}

Ok(())
}

pub async fn check_app_version(db: &DbPool) -> Result<Option<Version>, ErrorResponse> {
let app_version = Self::app_version();
debug!("Current Rauthy Version: {:?}", app_version);

// check DB version for compatibility
let db_version = match Self::find(db).await {
None => {
debug!(" No Current DB Version found");
// check if the DB is completely new
let db_exists = query!("select id from config limit 1").fetch_one(db).await;
if db_exists.is_ok() {
Self::is_db_compatible(db, &app_version, None).await?;
}

None
}
Some(db_version) => {
debug!("Current DB Version: {:?}", db_version);
Self::is_db_compatible(db, &app_version, Some(&db_version.version)).await?;

Some(db_version.version)
}
};

Ok(db_version)
}

/// Checks if we can use an existing (possibly older) db with this version of rauthy, or if
/// the user may need to take action beforehand.
async fn is_db_compatible(
db: &DbPool,
app_version: &Version,
db_version: Option<&Version>,
) -> Result<(), ErrorResponse> {
// this check panics on purpose and it is there to never forget to adjust this
// version check before doing any major or minor release
if app_version.major != 0 || app_version.minor != 16 {
panic!(
"\nDbVersion::check_app_version needs adjustment for the new RAUTHY_VERSION: {}",
RAUTHY_VERSION
);
}

// warn on prerelease usage
if !app_version.pre.is_empty() {
warn!("!!! Caution: you are using a prerelease version !!!");
}

// check for the lowest DB version we can use with this App Version
if let Some(db_version) = db_version {
if db_version.major != 0 || db_version.minor < 15 || db_version.minor > 16 {
panic!(
"\nRauthy {} needs at least a DB version v0.15 and max v0.16",
app_version
);
}

return Ok(());
}

// check the DB version in another way if we did not find an existing DB version

// the passkeys table was introduced with v0.15.0
let passkeys_exist = query!("select user_id from passkeys limit 1")
.fetch_one(db)
.await;
if passkeys_exist.is_err() {
panic!("\nYou need to start at least rauthy v0.15 before you can upgrade");
}

Ok(())
}
}

impl DbVersion {
pub fn app_version() -> Version {
Version::from_str(RAUTHY_VERSION).expect("bad format for RAUTHY_VERSION")
}
}
1 change: 1 addition & 0 deletions rauthy-models/src/entity/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod auth_codes;
pub mod clients;
pub mod colors;
pub mod db_version;
pub mod groups;
pub mod jwk;
pub mod magic_links;
Expand Down

0 comments on commit d2d9271

Please sign in to comment.