aboutsummaryrefslogtreecommitdiff
path: root/crates/secd
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/secd/src/auth/n.rs91
-rw-r--r--crates/secd/src/auth/z/mod.rs3
-rw-r--r--crates/secd/src/client/store/mod.rs53
-rw-r--r--crates/secd/src/client/store/sql_db.rs70
-rw-r--r--crates/secd/src/lib.rs85
-rw-r--r--crates/secd/store/pg/migrations/20221222002434_bootstrap.sql33
-rw-r--r--crates/secd/store/pg/sql/find_impersonator.sql10
-rw-r--r--crates/secd/store/pg/sql/write_impersonator.sql11
-rw-r--r--crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql21
-rw-r--r--crates/secd/store/sqlite/sql/find_credential.sql2
-rw-r--r--crates/secd/store/sqlite/sql/find_identity.sql2
-rw-r--r--crates/secd/store/sqlite/sql/find_impersonator.sql10
-rw-r--r--crates/secd/store/sqlite/sql/find_session.sql11
-rw-r--r--crates/secd/store/sqlite/sql/write_credential.sql4
-rw-r--r--crates/secd/store/sqlite/sql/write_impersonator.sql11
-rw-r--r--crates/secd/store/sqlite/sql/write_session.sql11
16 files changed, 311 insertions, 117 deletions
diff --git a/crates/secd/src/auth/n.rs b/crates/secd/src/auth/n.rs
index dde6e7d..12a5411 100644
--- a/crates/secd/src/auth/n.rs
+++ b/crates/secd/src/auth/n.rs
@@ -5,12 +5,13 @@ use crate::{
DEFAULT_SIGNUP_EMAIL,
},
store::{
- AddressLens, AddressValidationLens, CredentialLens, IdentityLens, Storable, StoreError,
+ AddressLens, AddressValidationLens, CredentialLens, IdentityLens, ImpersonatorLens,
+ Storable, StoreError,
},
},
util::{self, ErrorContext},
Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod,
- Credential, CredentialId, CredentialType, Identity, IdentityId, Secd, SecdError,
+ Credential, CredentialId, CredentialType, Identity, IdentityId, Impersonator, Secd, SecdError,
ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS,
ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION,
};
@@ -19,6 +20,7 @@ use log::warn;
use rand::Rng;
use std::str::FromStr;
use time::{Duration, OffsetDateTime};
+use tokio::join;
use uuid::Uuid;
impl Secd {
@@ -363,13 +365,13 @@ impl Secd {
Ok(identity)
}
- pub async fn validate_credential(&self, t: CredentialType) -> Result<Credential, SecdError> {
+ pub async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError> {
let mut retrieved = Credential::find(
self.store.clone(),
&CredentialLens {
id: None,
identity_id: None,
- t: Some(&t),
+ t: Some(t),
},
)
.await?
@@ -396,7 +398,7 @@ impl Secd {
retrieved.hash_compare(&t, &self.crypter)?;
// Return the initially provided plaintext credential since it's valid
- retrieved.t = t;
+ retrieved.t = t.clone();
Ok(retrieved)
}
@@ -503,4 +505,83 @@ impl Secd {
credential.write(self.store.clone()).await?;
Ok(())
}
+
+ pub async fn impersonate(
+ &self,
+ impersonator_id: &IdentityId,
+ target_id: &IdentityId,
+ ) -> Result<Credential, SecdError> {
+ let impersonator_lens = IdentityLens {
+ id: Some(&impersonator_id),
+ address_type: None,
+ validated_address: None,
+ };
+ let target_lens = IdentityLens {
+ id: Some(&target_id),
+ address_type: None,
+ validated_address: None,
+ };
+ let (i, t) = join!(
+ Identity::find(self.store.clone(), &impersonator_lens,),
+ Identity::find(self.store.clone(), &target_lens,)
+ );
+
+ let (i, t) = (
+ i.ctx("failed to retrieve impersonator identity")?,
+ t.ctx("failed to retrieve target identity")?,
+ );
+ if i.is_empty() || t.is_empty() {
+ return Err(SecdError::IdentityNotFound)
+ .ctx("failed to retrieve impersonator or target identity for impersonation");
+ }
+
+ let existing_impersonation = Impersonator::find(
+ self.store.clone(),
+ &ImpersonatorLens {
+ impersonator_id: Some(impersonator_id),
+ target_id: Some(target_id),
+ },
+ )
+ .await
+ .ctx("failed to find existing impersonation")?;
+
+ // TODO: We could expire the session chain, but I think we want to handle this more intelligently.
+ // For now, just revoke the credential manually...pita I know...
+ if !existing_impersonation.is_empty() {
+ return Err(SecdError::ImpersonatorAlreadyExists)
+ .ctx("Target already being impersonated by the provided impersonator identity");
+ }
+
+ let new_identity = self
+ .create_credential(
+ Credential::new_session(*target_id)?.t,
+ Some(*target_id),
+ OffsetDateTime::now_utc().checked_add(Duration::minutes(30)),
+ )
+ .await
+ .ctx("failed to create new credential for target identity")?;
+
+ let new_session = new_identity
+ .new_credentials
+ .iter()
+ .next()
+ .ok_or(SecdError::InvalidCredential)
+ .ctx("failed to retrieve new session from newly created target credential")?
+ .clone();
+
+ Impersonator {
+ impersonator: i
+ .into_iter()
+ .next()
+ .ok_or(SecdError::IdentityNotFound)
+ .ctx("failed to find impersonator identity")?,
+ target: new_identity,
+ created_at: OffsetDateTime::now_utc(),
+ }
+ .write(self.store.clone())
+ .await
+ .ctx("failed to write new impersonator")?;
+
+ Ok(new_session)
+ }
}
diff --git a/crates/secd/src/auth/z/mod.rs b/crates/secd/src/auth/z/mod.rs
index d64f674..bde319a 100644
--- a/crates/secd/src/auth/z/mod.rs
+++ b/crates/secd/src/auth/z/mod.rs
@@ -49,9 +49,6 @@ impl Authorization for Secd {
.map(|e| Uuid::parse_str(e).unwrap())
.collect())
}
- async fn check_list_subjects(&self) -> Result<Vec<i32>, SecdError> {
- unimplemented!()
- }
async fn write(&self, ts: &[Relationship]) -> Result<(), SecdError> {
let spice = self
.spice
diff --git a/crates/secd/src/client/store/mod.rs b/crates/secd/src/client/store/mod.rs
index 6c42dba..f08aa41 100644
--- a/crates/secd/src/client/store/mod.rs
+++ b/crates/secd/src/client/store/mod.rs
@@ -1,4 +1,4 @@
-pub(crate) mod sql_db;
+pub mod sql_db;
use async_trait::async_trait;
use sqlx::{Postgres, Sqlite};
@@ -7,7 +7,7 @@ use uuid::Uuid;
use crate::{
Address, AddressType, AddressValidation, Credential, CredentialId, CredentialType, Identity,
- IdentityId,
+ IdentityId, Impersonator,
};
use self::sql_db::SqlClient;
@@ -19,6 +19,7 @@ pub enum StoreError {
ParseError(#[from] strum::ParseError),
StoreValueCannotBeParsedInvariant,
IdempotentCheckAlreadyExists,
+ ExpectedEntity,
}
#[async_trait]
@@ -32,7 +33,7 @@ pub enum StoreType {
}
#[async_trait]
-pub(crate) trait Storable<'a> {
+pub trait Storable<'a> {
type Item;
type Lens;
@@ -43,33 +44,39 @@ pub(crate) trait Storable<'a> {
) -> Result<Vec<Self::Item>, StoreError>;
}
-pub(crate) trait Lens {}
+pub trait Lens {}
-pub(crate) struct AddressLens<'a> {
+pub struct AddressLens<'a> {
pub id: Option<&'a Uuid>,
pub t: Option<&'a AddressType>,
}
impl<'a> Lens for AddressLens<'a> {}
-pub(crate) struct AddressValidationLens<'a> {
+pub struct AddressValidationLens<'a> {
pub id: Option<&'a Uuid>,
}
impl<'a> Lens for AddressValidationLens<'a> {}
-pub(crate) struct IdentityLens<'a> {
+pub struct IdentityLens<'a> {
pub id: Option<&'a Uuid>,
pub address_type: Option<&'a AddressType>,
pub validated_address: Option<bool>,
}
impl<'a> Lens for IdentityLens<'a> {}
-pub(crate) struct CredentialLens<'a> {
+pub struct CredentialLens<'a> {
pub id: Option<CredentialId>,
pub identity_id: Option<IdentityId>,
pub t: Option<&'a CredentialType>,
}
impl<'a> Lens for CredentialLens<'a> {}
+pub struct ImpersonatorLens<'a> {
+ pub impersonator_id: Option<&'a IdentityId>,
+ pub target_id: Option<&'a IdentityId>,
+}
+impl<'a> Lens for ImpersonatorLens<'a> {}
+
#[async_trait]
impl<'a> Storable<'a> for Address {
type Item = Address;
@@ -179,3 +186,33 @@ impl<'a> Storable<'a> for Credential {
})
}
}
+
+#[async_trait]
+impl<'a> Storable<'a> for Impersonator {
+ type Item = Impersonator;
+ type Lens = ImpersonatorLens<'a>;
+
+ async fn write(&self, store: Arc<dyn Store>) -> Result<(), StoreError> {
+ match store.get_type() {
+ StoreType::Postgres { c } => c.write_impersonator(self).await?,
+ StoreType::Sqlite { c } => c.write_impersonator(self).await?,
+ }
+ Ok(())
+ }
+
+ async fn find(
+ store: Arc<dyn Store>,
+ lens: &'a Self::Lens,
+ ) -> Result<Vec<Self::Item>, StoreError> {
+ Ok(match store.get_type() {
+ StoreType::Postgres { c } => {
+ c.find_impersonator(lens.impersonator_id, lens.target_id)
+ .await?
+ }
+ StoreType::Sqlite { c } => {
+ c.find_impersonator(lens.impersonator_id, lens.target_id)
+ .await?
+ }
+ })
+ }
+}
diff --git a/crates/secd/src/client/store/sql_db.rs b/crates/secd/src/client/store/sql_db.rs
index 7b3a68e..5777704 100644
--- a/crates/secd/src/client/store/sql_db.rs
+++ b/crates/secd/src/client/store/sql_db.rs
@@ -1,4 +1,5 @@
use super::{Store, StoreError, StoreType};
+use crate::Impersonator;
use crate::{
util::ErrorContext, Address, AddressType, AddressValidation, AddressValidationMethod,
Credential, CredentialId, CredentialType, Identity, IdentityId,
@@ -26,6 +27,8 @@ const WRITE_CREDENTIAL: &str = "write_credential";
const FIND_CREDENTIAL: &str = "find_credential";
const WRITE_IDENTITY: &str = "write_identity";
const FIND_IDENTITY: &str = "find_identity";
+const WRITE_IMPERSONATOR: &str = "write_impersonator";
+const FIND_IMPERSONATOR: &str = "find_impersonator";
const ERR_MSG_MIGRATION_FAILED: &str = "Failed to apply secd migrations to a sql db. File a bug at https://www.github.com/branchcontrol/secdiam";
@@ -64,6 +67,14 @@ lazy_static! {
FIND_CREDENTIAL,
include_str!("../../../store/sqlite/sql/find_credential.sql"),
),
+ (
+ WRITE_IMPERSONATOR,
+ include_str!("../../../store/sqlite/sql/write_impersonator.sql"),
+ ),
+ (
+ FIND_IMPERSONATOR,
+ include_str!("../../../store/sqlite/sql/find_impersonator.sql"),
+ ),
]
.iter()
.cloned()
@@ -102,6 +113,14 @@ lazy_static! {
FIND_CREDENTIAL,
include_str!("../../../store/pg/sql/find_credential.sql"),
),
+ (
+ WRITE_IMPERSONATOR,
+ include_str!("../../../store/pg/sql/write_impersonator.sql"),
+ ),
+ (
+ FIND_IMPERSONATOR,
+ include_str!("../../../store/pg/sql/find_impersonator.sql"),
+ ),
]
.iter()
.cloned()
@@ -525,6 +544,57 @@ where
Ok(res)
}
+
+ pub async fn write_impersonator(&self, i: &Impersonator) -> Result<(), StoreError> {
+ let sqls = get_sqls(&self.sqls_root, WRITE_IMPERSONATOR);
+ sqlx::query(&sqls[0])
+ .bind(i.impersonator.id)
+ .bind(i.target.id)
+ .bind(i.target.new_credentials.get(0).map(|e| &e.id))
+ .bind(i.created_at)
+ .fetch_all(&self.pool)
+ .await
+ .extend_err()?;
+ Ok(())
+ }
+ pub async fn find_impersonator(
+ &self,
+ impersonator_id: Option<&Uuid>,
+ target_id: Option<&Uuid>,
+ ) -> Result<Vec<Impersonator>, StoreError> {
+ let sqls = get_sqls(&self.sqls_root, FIND_IMPERSONATOR);
+ let rs = sqlx::query_as::<_, (Uuid, Uuid, OffsetDateTime)>(&sqls[0])
+ .bind(impersonator_id)
+ .bind(target_id)
+ .bind(OffsetDateTime::now_utc())
+ .fetch_all(&self.pool)
+ .await
+ .extend_err()?;
+
+ let mut res = vec![];
+ for (impersonator_id, target_id, created_at) in rs.into_iter() {
+ let impersonator = self
+ .find_identity(Some(&impersonator_id), None, None)
+ .await?
+ .into_iter()
+ .next()
+ .ok_or(StoreError::ExpectedEntity)?;
+ let target = self
+ .find_identity(Some(&target_id), None, None)
+ .await?
+ .into_iter()
+ .next()
+ .ok_or(StoreError::ExpectedEntity)?;
+
+ res.push(Impersonator {
+ impersonator,
+ target,
+ created_at,
+ })
+ }
+
+ Ok(res)
+ }
}
fn get_sqls(root: &str, file: &str) -> Vec<String> {
diff --git a/crates/secd/src/lib.rs b/crates/secd/src/lib.rs
index c84ce44..7fa1755 100644
--- a/crates/secd/src/lib.rs
+++ b/crates/secd/src/lib.rs
@@ -41,9 +41,7 @@ pub type AddressId = Uuid;
pub type AddressValidationId = Uuid;
pub type CredentialId = Uuid;
pub type IdentityId = Uuid;
-pub type MotifId = Uuid;
pub type PhoneNumber = String;
-pub type RefId = Uuid;
#[derive(Debug, derive_more::Display, thiserror::Error)]
pub enum SecdError {
@@ -60,10 +58,15 @@ pub enum SecdError {
CrypterError(#[from] CrypterError),
+ CfgMissingSpiceSecret,
+ CfgMissingSpiceServer,
+
TooManyIdentities,
IdentityNotFound,
IdentityAlreadyExists,
+ ImpersonatorAlreadyExists,
+
EmailMessengerError(#[from] EmailMessengerError),
InvalidEmaillAddress(#[from] email_address::Error),
@@ -103,43 +106,40 @@ struct Cfg {
email_signup_message_asset_loc: Option<String>,
email_signin_message: Option<String>,
email_signup_message: Option<String>,
- spice_secret: String,
- spice_server: String,
+ spice_secret: Option<String>,
+ spice_server: Option<String>,
}
#[async_trait]
pub trait Authentication {
- async fn validate_address(
- &self,
- address_type: AddressType,
- identity_id: Option<IdentityId>,
- ) -> Result<AddressValidation, SecdError>;
-
- async fn complete_address_validation(
- &self,
- validation_id: &AddressValidationId,
- plaintext_token: Option<String>,
- plaintext_code: Option<String>,
- ) -> Result<AddressValidation, SecdError>;
-
+ async fn check_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>;
async fn create_credential(
&self,
t: &CredentialType,
identity_id: Option<IdentityId>,
- ) -> Result<IdentityId, SecdError>;
- // async fn update_credential(&self, t: &CredentialType) -> Result<(), SecdError>;
- async fn reset_credential(
+ expires_at: Option<OffsetDateTime>,
+ ) -> Result<Identity, SecdError>;
+ async fn create_identity(
&self,
+ i: &Identity,
t: &CredentialType,
- address: &AddressType,
+ md: Option<String>,
+ ) -> Result<Identity, SecdError>;
+ async fn impersonate(
+ &self,
+ impersonator: &Identity,
+ target: &Identity,
) -> Result<Credential, SecdError>;
- async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>;
-
- // async fn expire_session_chain(&self, t: &SessionToken) -> Result<(), SecdError>;
- // async fn expire_sessions(&self, i: &IdentityId) -> Result<(), SecdError>;
-
+ async fn revoke_credential(&self, credential_id: &CredentialId) -> Result<Identity, SecdError>;
+ async fn send_address_validation(&self, t: AddressType)
+ -> Result<AddressValidation, SecdError>;
+ async fn validate_address(
+ &self,
+ v_id: &AddressValidationId,
+ plaintext_token: Option<String>,
+ plaintext_code: Option<String>,
+ ) -> Result<AddressValidation, SecdError>;
// async fn get_identity(&self, t: &SessionToken) -> Result<Identity, SecdError>;
- // async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError>;
}
#[async_trait]
@@ -151,7 +151,6 @@ pub trait Authorization {
subj: &Subject,
relation: &Relation,
) -> Result<Vec<Uuid>, SecdError>;
- async fn check_list_subjects(&self) -> Result<Vec<i32>, SecdError>;
async fn write(&self, relationships: &[Relationship]) -> Result<(), SecdError>;
}
@@ -218,7 +217,7 @@ pub enum AddressType {
}
#[serde_as]
-#[derive(Debug, Serialize)]
+#[derive(Clone, Debug, Serialize)]
pub struct Credential {
pub id: CredentialId,
pub identity_id: IdentityId,
@@ -263,6 +262,15 @@ pub struct Identity {
pub deleted_at: Option<OffsetDateTime>,
}
+#[serde_with::skip_serializing_none]
+#[derive(Debug, Serialize)]
+pub struct Impersonator {
+ pub impersonator: Identity,
+ pub target: Identity,
+ #[serde(with = "time::serde::timestamp")]
+ pub created_at: OffsetDateTime,
+}
+
impl Cfg {
fn resolve(&mut self) -> Result<(), SecdError> {
if let Some(path) = &self.email_signin_message_asset_loc {
@@ -313,8 +321,8 @@ impl Secd {
CRYPTER_SECRET_KEY_DEFAULT.to_string()
});
- info!("starting client with auth_store: {:?}", auth_store);
- info!("starting client with email_messenger: {:?}", auth_store);
+ info!("init with auth_store: {:?}", auth_store);
+ info!("init with email_messenger: {:?}", email_messenger);
let store = match auth_store {
AuthStore::Sqlite { conn } => {
@@ -340,7 +348,7 @@ impl Secd {
.connect(&conn)
.await
.map_err(|e| {
- SecdError::StoreInitFailure(format!("failed to init sqlite: {}", e))
+ SecdError::StoreInitFailure(format!("failed to init postgres: {}", e))
})?,
)
.await
@@ -366,8 +374,17 @@ impl Secd {
let spice = match z_schema {
Some(schema) => {
- let c: Arc<Spice> =
- Arc::new(Spice::new(cfg.spice_secret.clone(), cfg.spice_server.clone()).await);
+ let c: Arc<Spice> = Arc::new(
+ Spice::new(
+ cfg.spice_secret
+ .clone()
+ .ok_or(SecdError::CfgMissingSpiceSecret)?,
+ cfg.spice_server
+ .clone()
+ .ok_or(SecdError::CfgMissingSpiceServer)?,
+ )
+ .await,
+ );
c.write_schema(schema)
.await
.unwrap_or_else(|_| panic!("{}", "failed to write authorization schema"));
diff --git a/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql b/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql
index 0fd423e..8f0a9c0 100644
--- a/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql
+++ b/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql
@@ -66,16 +66,6 @@ create table if not exists secd.address_validation (
, unique(address_validation_public_id)
);
-create table if not exists secd.session (
- session_id bigserial primary key
- , identity_id bigint not null references secd.identity(identity_id)
- , token_hash bytea not null
- , created_at timestamptz not null
- , expired_at timestamptz not null
- , revoked_at timestamptz
- , unique(token_hash)
-);
-
create table if not exists secd.message (
message_id bigserial primary key
, address_id bigint not null references secd.address(address_id)
@@ -87,22 +77,9 @@ create table if not exists secd.message (
, sent_at timestamptz
);
-create table if not exists secd.namespace_config (
- namespace text not null
- , serialized_config text not null
- , created_at xid8 not null
- , deleted_at xid8
- -- TODO: indexes and stuff
-);
-
-create table if not exists secd.relation_tuple (
- namespace text not null
- , object_id text not null
- , relation text not null
- , userset_namespace text not null
- , userset_object_id text not null
- , userset_relation text not null
- , created_at xid8 not null
- , deleted_at xid8 not null
- -- TODO: indexes and stuff
+create table if not exists secd.impersonator (
+ impersonator_id bigint not null references secd.identity(identity_id)
+ , target_id bigint not null references secd.identity(identity_id)
+ , credential_id bigint not null references secd.credential(credential_id)
+ , created_at timestamptz not null
);
diff --git a/crates/secd/store/pg/sql/find_impersonator.sql b/crates/secd/store/pg/sql/find_impersonator.sql
new file mode 100644
index 0000000..e544598
--- /dev/null
+++ b/crates/secd/store/pg/sql/find_impersonator.sql
@@ -0,0 +1,10 @@
+select i2.identity_public_id as impersonator_public_id
+ , i3.identity_public_id as target_public_id
+ , i.created_at
+from secd.impersonator i
+join secd.identity i2 on i.impersonator_id = i2.identity_id
+join secd.identity i3 on i.target_id = i3.identity_id
+join secd.credential c using (credential_id)
+where (($1::uuid is null) or (i2.identity_public_id = $1))
+and (($2::uuid is null) or (i3.identity_public_id = $2))
+and c.revoked_at > $3;
diff --git a/crates/secd/store/pg/sql/write_impersonator.sql b/crates/secd/store/pg/sql/write_impersonator.sql
new file mode 100644
index 0000000..b67b738
--- /dev/null
+++ b/crates/secd/store/pg/sql/write_impersonator.sql
@@ -0,0 +1,11 @@
+insert into secd.impersonator (
+ impersonator_id
+ , target_id
+ , credential_id
+ , created_at
+) values (
+ (select identity_id from secd.identity where identity_public_id = $1)
+ , (select identity_id from secd.identity where identity_public_id = $2)
+ , (select credential_id from secd.credential where credential_public_id = $3)
+ , $4
+);
diff --git a/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql b/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql
index b2ce45d..0a182e1 100644
--- a/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql
+++ b/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql
@@ -34,9 +34,7 @@ create table if not exists credential (
, deleted_at integer
);
-create unique index if not exists credential_passphrase_type_key_ix
-on credential (partial_key)
-where type = 'Passphrase';
+create unique index if not exists credential_partial_key_type_key_ix on credential (partial_key);
create table if not exists address (
address_id integer primary key
@@ -63,16 +61,6 @@ create table if not exists address_validation (
, unique(address_validation_public_id)
);
-create table if not exists session (
- session_id integer primary key
- , identity_id integer not null references identity(identity_id)
- , token_hash blob not null
- , created_at integer not null
- , expired_at integer not null
- , revoked_at integer
- , unique(token_hash)
-);
-
create table if not exists message (
message_id integer primary key
, address_id integer not null references address(address_id)
@@ -83,3 +71,10 @@ create table if not exists message (
, created_at integer not null
, sent_at integer
);
+
+create table if not exists impersonator (
+ impersonator_id integer not null references identity(identity_id)
+ , target_id integer not null references identity(identity_id)
+ , credential_id integer not null references credential(credential_id)
+ , created_at integer not null
+);
diff --git a/crates/secd/store/sqlite/sql/find_credential.sql b/crates/secd/store/sqlite/sql/find_credential.sql
index 9062914..0590dee 100644
--- a/crates/secd/store/sqlite/sql/find_credential.sql
+++ b/crates/secd/store/sqlite/sql/find_credential.sql
@@ -9,4 +9,4 @@ join identity i using (identity_id)
where (($1 is null) or (c.credential_public_id = $1))
and (($2 is null) or (i.identity_public_id = $2))
and (($3 is null) or (c.type = $3))
-and (($3 is null or $4 is null) or (c.data->$3->>'key' = $4))
+and (($3 is null or $4 is null) or (c.partial_key = $4))
diff --git a/crates/secd/store/sqlite/sql/find_identity.sql b/crates/secd/store/sqlite/sql/find_identity.sql
index 1528407..0d32a9b 100644
--- a/crates/secd/store/sqlite/sql/find_identity.sql
+++ b/crates/secd/store/sqlite/sql/find_identity.sql
@@ -7,9 +7,7 @@ select distinct
from identity i
left join address_validation av using (identity_id)
left join address a using (address_id)
-left join session s using (identity_id)
where (($1 is null) or (i.identity_public_id = $1))
and (($2 is null) or (a.value = $2))
and (($3 is null) or (($3 is true) and (av.validated_at is not null)))
-and (($4 is null) or (s.token_hash = $4))
and i.deleted_at is null;
diff --git a/crates/secd/store/sqlite/sql/find_impersonator.sql b/crates/secd/store/sqlite/sql/find_impersonator.sql
new file mode 100644
index 0000000..786e9ba
--- /dev/null
+++ b/crates/secd/store/sqlite/sql/find_impersonator.sql
@@ -0,0 +1,10 @@
+select i2.identity_public_id as impersonator_public_id
+ , i3.identity_public_id as target_public_id
+ , i.created_at
+from impersonator i
+join identity i2 on i.impersonator_id = i2.identity_id
+join identity i3 on i.target_id = i3.identity_id
+join credential c using (credential_id)
+where (($1 is null) or (i2.identity_public_id = $1))
+and (($2 is null) or (i3.identity_public_id = $2))
+and c.revoked_at > $3;
diff --git a/crates/secd/store/sqlite/sql/find_session.sql b/crates/secd/store/sqlite/sql/find_session.sql
deleted file mode 100644
index 31640dd..0000000
--- a/crates/secd/store/sqlite/sql/find_session.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-select distinct
- i.identity_public_id
- , s.created_at
- , s.expired_at
- , s.revoked_at
-from session s
-join identity i using (identity_id)
-where (($1 is null) or (s.token_hash = $1))
-and (($2 is null) or (i.identity_public_id = $2))
-and (($3 is null) or (s.expired_at > $3))
-and ((revoked_at is null) or ($4 is null) or (s.revoked_at > $4));
diff --git a/crates/secd/store/sqlite/sql/write_credential.sql b/crates/secd/store/sqlite/sql/write_credential.sql
index 3319226..06cb389 100644
--- a/crates/secd/store/sqlite/sql/write_credential.sql
+++ b/crates/secd/store/sqlite/sql/write_credential.sql
@@ -16,4 +16,6 @@ insert into credential (
, $6
, $7
, $8
-);
+) on conflict (partial_key) do update
+ set revoked_at = excluded.revoked_at
+ , deleted_at = excluded.deleted_at;
diff --git a/crates/secd/store/sqlite/sql/write_impersonator.sql b/crates/secd/store/sqlite/sql/write_impersonator.sql
new file mode 100644
index 0000000..ae81466
--- /dev/null
+++ b/crates/secd/store/sqlite/sql/write_impersonator.sql
@@ -0,0 +1,11 @@
+insert into impersonator (
+ impersonator_id
+ , target_id
+ , credential_id
+ , created_at
+) values (
+ (select identity_id from identity where identity_public_id = $1)
+ , (select identity_id from identity where identity_public_id = $2)
+ , (select credential_id from credential where credential_public_id = $3)
+ , $4
+);
diff --git a/crates/secd/store/sqlite/sql/write_session.sql b/crates/secd/store/sqlite/sql/write_session.sql
deleted file mode 100644
index 9ffb105..0000000
--- a/crates/secd/store/sqlite/sql/write_session.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-insert into session (
- identity_id
- , token_hash
- , created_at
- , expired_at
- , revoked_at
-) values (
- (select identity_id from identity where identity_public_id = $1)
- , $2, $3, $4, $5
-) on conflict (token_hash) do update
- set revoked_at = excluded.revoked_at;