Bläddra i källkod

Remove homegrown JWT/JWKS code, migrated token code to RealmHelper.

Kestrel 11 månader sedan
förälder
incheckning
408dbf147f
15 ändrade filer med 507 tillägg och 541 borttagningar
  1. 105 5
      Cargo.lock
  2. 3 1
      Cargo.toml
  3. 23 14
      src/cli.rs
  4. 2 2
      src/client_management.rs
  5. 8 1
      src/config.rs
  6. 0 4
      src/error.rs
  7. 0 157
      src/jwt.rs
  8. 57 188
      src/key.rs
  9. 1 3
      src/main.rs
  10. 248 0
      src/realm.rs
  11. 3 3
      src/schema.rs
  12. 8 1
      src/server.rs
  13. 43 39
      src/server/oidc.rs
  14. 0 122
      src/token.rs
  15. 6 1
      src/token_management.rs

+ 105 - 5
Cargo.lock

@@ -415,6 +415,18 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
+
 [[package]]
 name = "bincode"
 version = "1.3.3"
@@ -957,8 +969,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
  "cfg-if 1.0.0",
+ "js-sys",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
 ]
 
 [[package]]
@@ -1205,6 +1219,21 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "jsonwebtoken"
+version = "9.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f"
+dependencies = [
+ "base64 0.21.7",
+ "js-sys",
+ "pem",
+ "ring 0.17.8",
+ "serde",
+ "serde_json",
+ "simple_asn1",
+]
+
 [[package]]
 name = "kv-log-macro"
 version = "1.0.7"
@@ -1310,12 +1339,32 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "num-conv"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
 
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 name = "num-traits"
 version = "0.2.17"
@@ -1366,6 +1415,16 @@ dependencies = [
  "windows-targets 0.48.5",
 ]
 
+[[package]]
+name = "pem"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
+dependencies = [
+ "base64 0.22.0",
+ "serde",
+]
+
 [[package]]
 name = "percent-encoding"
 version = "2.3.0"
@@ -1682,12 +1741,27 @@ dependencies = [
  "cc",
  "libc",
  "once_cell",
- "spin",
- "untrusted",
+ "spin 0.5.2",
+ "untrusted 0.7.1",
  "web-sys",
  "winapi",
 ]
 
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if 1.0.0",
+ "getrandom 0.2.10",
+ "libc",
+ "spin 0.9.8",
+ "untrusted 0.9.0",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "route-recognizer"
 version = "0.2.0"
@@ -1818,9 +1892,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.114"
+version = "1.0.112"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
 dependencies = [
  "itoa",
  "ryu",
@@ -1948,6 +2022,18 @@ dependencies = [
  "event-listener 2.5.3",
 ]
 
+[[package]]
+name = "simple_asn1"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "thiserror",
+ "time 0.3.34",
+]
+
 [[package]]
 name = "slab"
 version = "0.4.9"
@@ -1996,6 +2082,12 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
 [[package]]
 name = "standback"
 version = "0.2.17"
@@ -2360,16 +2452,18 @@ version = "0.0.2"
 dependencies = [
  "base32",
  "base64 0.13.1",
+ "bincode",
  "clap",
  "handlebars",
  "hmac 0.12.1",
  "itertools",
+ "jsonwebtoken",
  "lazy_static",
  "log",
  "microrm",
  "pretty_env_logger",
  "qr2term",
- "ring",
+ "ring 0.16.20",
  "rpassword",
  "serde",
  "serde_bytes",
@@ -2419,6 +2513,12 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
 [[package]]
 name = "url"
 version = "2.4.1"

+ 3 - 1
Cargo.toml

@@ -15,12 +15,13 @@ time = { version = "0.3", features = ["std", "formatting"] }
 itertools = "0.12"
 
 # crypto
-ring = { version = "0.16.20", features = ["std"] }
+ring = { version = "0.16", features = ["std"] }
 sha1 = { version = "0.10" }
 sha2 = { version = "0.10" }
 base32 = { version = "0.4.0" }
 base64 = { version = "0.13.0" }
 hmac = { version = "0.12" }
+bincode = "1.3"
 
 # configuration
 toml = "0.8.2"
@@ -33,6 +34,7 @@ serde_bytes = { version = "0.11.6" }
 tide = { version = "0.16.0" }
 handlebars = { version = "4.3", features = ["dir_source"] }
 serde_json = "1.0"
+jsonwebtoken = "9.3"
 
 # CLI dependencies
 clap = { version = "4.5", features = ["derive", "env", "string"] }

+ 23 - 14
src/cli.rs

@@ -1,8 +1,9 @@
 use crate::{
     config,
     key::{self, KeyType},
+    realm::RealmHelper,
     schema::{self, UIDCDatabase},
-    server, token_management, UIDCError,
+    server, UIDCError,
 };
 use clap::{Parser, Subcommand};
 use microrm::cli::Autogenerate;
@@ -250,18 +251,25 @@ enum TokenCommand {
 impl TokenCommand {
     async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
         let config = config::Config::build_from(&args.db, None);
+
+        let get_stored = |client_name: &str, user_name: &str| {
+            let stored_client = args.realm.clients.with(schema::Client::Shortname, client_name).first().get()?.ok_or(UIDCError::Abort("no such client"))?;
+            let stored_user = args.realm.users.with(schema::User::Username, user_name).first().get()?.ok_or(UIDCError::Abort("no such user"))?;
+            Result::<_, UIDCError>::Ok((stored_client, stored_user))
+        };
+
         match self {
             TokenCommand::GenerateAuth {
                 client,
                 username,
                 scopes,
             } => {
-                let token = token_management::create_auth_token(
-                    &args.realm,
-                    &config,
-                    &client,
-                    &username,
-                    &scopes,
+                let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
+                let realm = RealmHelper::new(config, args.realm);
+                let token = realm.generate_access_token(
+                    &stored_client,
+                    &stored_user,
+                    scopes.split(" "),
                 )?;
                 println!("{}", token);
                 Ok(())
@@ -271,18 +279,19 @@ impl TokenCommand {
                 username,
                 scopes,
             } => {
-                let token = token_management::create_refresh_token(
-                    &args.realm,
-                    &config,
-                    &client,
-                    &username,
-                    &scopes,
+                let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
+                let realm = RealmHelper::new(config, args.realm);
+                let token = realm.generate_refresh_token(
+                    &stored_client,
+                    &stored_user,
+                    scopes.split(" "),
                 )?;
                 println!("{}", token);
                 Ok(())
             }
             TokenCommand::Inspect { token } => {
-                token_management::inspect_token(&config, &args.realm, token.as_ref())
+                todo!()
+                // token_management::inspect_token(&config, &args.realm, token.as_ref())
             }
         }
     }

+ 2 - 2
src/client_management.rs

@@ -1,4 +1,4 @@
-use crate::{key::KeyType, schema, UIDCError};
+use crate::{key::{HMacType, KeyType}, schema, UIDCError};
 use microrm::prelude::*;
 
 pub fn create(
@@ -15,7 +15,7 @@ pub fn create(
         shortname: name.into(),
         secret: base64::encode(&client_secret),
         access_key_type: key_type.into(),
-        refresh_key_type: key_type.into(),
+        refresh_key_type: KeyType::HMac(HMacType::Sha256).into_serialized(),
         refresh_token_secret: refresh_token_secret.into(),
         redirects: Default::default(),
         scopes: Default::default(),

+ 8 - 1
src/config.rs

@@ -12,7 +12,11 @@ fn default_auth_code_expiry() -> u64 {
     600
 }
 
-#[derive(Debug, Serialize, Deserialize)]
+fn default_refresh_token_expiry() -> u64 {
+    3600
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Config {
     pub base_url: String,
 
@@ -21,6 +25,9 @@ pub struct Config {
 
     #[serde(default = "default_auth_code_expiry")]
     pub auth_code_expiry: u64,
+
+    #[serde(default = "default_refresh_token_expiry")]
+    pub refresh_token_expiry: u64,
 }
 
 impl Config {

+ 0 - 4
src/error.rs

@@ -15,10 +15,6 @@ pub enum UIDCError {
     /// error with key generation or message signing
     KeyError(KeyError),
 
-    /*
-    /// error with token generation or verification
-    TokenError(TokenError),
-    */
     /// error with user operation
     UserError(UserError),
 }

+ 0 - 157
src/jwt.rs

@@ -1,157 +0,0 @@
-use crate::key::{HMacType, ParsedKey};
-
-fn jwt_algorithm(pk: &ParsedKey) -> &'static str {
-    match pk {
-        ParsedKey::HMAC { hmty, .. } => match hmty {
-            HMacType::Sha256 => "HS256",
-            HMacType::Sha512 => "HS512",
-        },
-        ParsedKey::Ed25519 { .. } => "EdDSA",
-        ParsedKey::RSA { .. } => "RS256",
-    }
-}
-
-#[derive(serde::Serialize, serde::Deserialize)]
-pub struct JWTHeader {
-    pub alg: Option<String>,
-    pub typ: String,
-    pub kid: Option<String>,
-}
-
-impl JWTHeader {
-    pub fn parse(full_jwt: &str) -> Option<Self> {
-        let header_raw = full_jwt.split(".").next()?;
-        let header_decoded =
-            base64::decode_config(header_raw.as_bytes(), base64::URL_SAFE_NO_PAD).ok()?;
-
-        serde_json::from_slice(header_decoded.as_slice()).ok()
-    }
-}
-
-#[derive(serde::Serialize, serde::Deserialize)]
-pub struct JWTData<'l> {
-    pub sub: &'l str,
-    pub iss: &'l str,
-    pub aud: &'l str,
-    pub iat: u64,
-    pub exp: u64,
-
-    #[serde(flatten)]
-    pub extras: std::collections::HashMap<&'l str, serde_json::Value>,
-}
-
-impl<'l> Into<String> for JWTData<'l> {
-    fn into(self) -> String {
-        serde_json::to_string(&self).expect("JSON serialization failure?")
-    }
-}
-
-pub struct JWT {
-    pub header: String,
-    pub unencoded_data: Vec<u8>,
-    pub encoded_data: String,
-    pub signature: String,
-}
-
-impl JWT {
-    pub fn verify(with: &ParsedKey, from: &str) -> Option<Self> {
-        let header_split = from.find(".")?;
-        let header = &from[0..header_split];
-        let data_split = header_split + 1 + from[header_split + 1..].find(".")?;
-        let data = &from[header_split + 1..data_split];
-        let signature = &from[data_split + 1..];
-
-        let mut to_verify = vec![];
-        to_verify.extend(header.as_bytes());
-        to_verify.extend(".".as_bytes());
-        to_verify.extend(data.as_bytes());
-
-        let decoded_signature =
-            base64::decode_config(signature.as_bytes(), base64::URL_SAFE_NO_PAD).ok()?;
-        with.verify_signature(to_verify.as_ref(), decoded_signature.as_ref())
-            .ok()?;
-
-        // if we got this far, the verification passed
-        Some(Self {
-            header: header.into(),
-            unencoded_data: base64::decode_config(data.as_bytes(), base64::URL_SAFE_NO_PAD).ok()?,
-            encoded_data: data.to_string(),
-            signature: signature.into(),
-        })
-    }
-
-    pub fn claims(&self) -> Option<JWTData> {
-        serde_json::from_slice(self.unencoded_data.as_slice()).ok()
-    }
-
-    pub fn sign(with: &ParsedKey, data: JWTData) -> Self {
-        let header = JWTHeader {
-            alg: Some(jwt_algorithm(with).into()),
-            typ: "JWT".into(),
-            kid: Some(with.key_id().into()),
-        };
-        let header_data = base64::encode_config(
-            serde_json::to_vec(&header).unwrap(),
-            base64::URL_SAFE_NO_PAD,
-        );
-
-        let unencoded_data = Into::<String>::into(data);
-        let data = base64::encode_config(unencoded_data.as_bytes(), base64::URL_SAFE_NO_PAD);
-
-        let mut to_sign = vec![];
-        to_sign.extend(header_data.as_bytes());
-        to_sign.extend(".".as_bytes());
-        to_sign.extend(data.as_bytes());
-        let signature = base64::encode_config(
-            with.generate_signature(&to_sign)
-                .expect("couldn't sign data"),
-            base64::URL_SAFE_NO_PAD,
-        );
-
-        Self {
-            header: header_data,
-            unencoded_data: unencoded_data.as_bytes().into(),
-            encoded_data: data,
-            signature,
-        }
-    }
-
-    pub fn into_string(self) -> String {
-        self.header + "." + self.encoded_data.as_str() + "." + self.signature.as_str()
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use ring::signature::KeyPair;
-
-    #[test]
-    fn simple_round_trip() {
-        let rng = ring::rand::SystemRandom::new();
-        let kpair_raw = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng)
-            .expect("couldn't generate ephemeral keypair");
-        let kpair = ring::signature::Ed25519KeyPair::from_pkcs8(kpair_raw.as_ref())
-            .expect("couldn't load keypair");
-
-        let jdata = super::JWTData {
-            sub: "sub",
-            iss: "iss",
-            aud: "aud",
-            iat: 1,
-            exp: 2,
-            extras: Default::default(),
-        };
-
-        let generated = super::JWT::sign(&kpair, jdata).into_string();
-        println!("generated: {:?}", generated);
-
-        let pubkey = ring::signature::UnparsedPublicKey::new(
-            &ring::signature::ED25519,
-            kpair.public_key().as_ref(),
-        );
-
-        let vresult = super::JWT::verify(&pubkey, generated.as_str());
-
-        assert!(vresult.is_some());
-    }
-}

+ 57 - 188
src/key.rs

@@ -1,5 +1,3 @@
-use std::{cell::RefCell, sync::Arc};
-
 use crate::{schema, UIDCError};
 use hmac::{Hmac, Mac};
 use microrm::prelude::*;
@@ -17,7 +15,7 @@ pub enum KeyError {
 }
 
 #[non_exhaustive]
-#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
 pub enum HMacType {
     Sha256,
     Sha512,
@@ -33,7 +31,7 @@ impl HMacType {
 }
 
 #[non_exhaustive]
-#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
 pub enum KeyType {
     HMac(HMacType),
     RSA2048,
@@ -70,172 +68,50 @@ impl std::str::FromStr for KeyType {
     }
 }
 
-pub enum ParsedKey {
-    HMAC {
-        key_id: String,
-        keydata: Vec<u8>,
-        hmty: HMacType,
-    },
-    Ed25519 {
-        key_id: String,
-        keypair: ring::signature::Ed25519KeyPair,
-    },
-    RSA {
-        key_id: String,
-        keypair: ring::signature::RsaKeyPair,
-    },
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+struct RsaPublicData<'l> {
+    modulus: &'l [u8],
+    exponent: &'l [u8],
 }
 
-impl ParsedKey {
-    pub fn key_id(&self) -> &str {
-        match self {
-            Self::HMAC { key_id, .. } => key_id.as_str(),
-            Self::Ed25519 { key_id, .. } => key_id.as_str(),
-            Self::RSA { key_id, .. } => key_id.as_str(),
-        }
-    }
-
-    pub fn generate_signature(&self, data: &[u8]) -> Result<Vec<u8>, UIDCError> {
-        match self {
-            Self::HMAC { keydata, hmty, .. } => match hmty {
-                HMacType::Sha256 => Ok(Hmac::<sha2::Sha256>::new_from_slice(keydata.as_slice())
-                    .unwrap()
-                    .chain_update(data)
-                    .finalize()
-                    .into_bytes()
-                    .to_vec()),
-                HMacType::Sha512 => Ok(Hmac::<sha2::Sha512>::new_from_slice(keydata.as_slice())
-                    .unwrap()
-                    .chain_update(data)
-                    .finalize()
-                    .into_bytes()
-                    .to_vec()),
+impl schema::Key {
+    pub fn into_jwk(self) -> jsonwebtoken::jwk::Jwk {
+        use jsonwebtoken::jwk;
+
+        match self.key_type.as_ref() {
+            KeyType::RSA2048 | KeyType::RSA4096 => {
+                let rpubdata : RsaPublicData = bincode::deserialize(self.public_data.as_slice()).unwrap();
+
+                jwk::Jwk {
+                    common: jwk::CommonParameters {
+                        public_key_use: Some(jwk::PublicKeyUse::Signature),
+                        key_algorithm: Some(jwk::KeyAlgorithm::RS256),
+                        key_id: Some(self.key_id),
+                        ..Default::default()
+                    },
+                    algorithm: jwk::AlgorithmParameters::RSA(jwk::RSAKeyParameters {
+                        key_type: jwk::RSAKeyType::RSA,
+                        n: base64::encode(rpubdata.modulus),
+                        e: base64::encode(rpubdata.exponent),
+                    })
+                }
             },
-            Self::Ed25519 { keypair, .. } => Ok(keypair.sign(data).as_ref().into()),
-            Self::RSA { keypair, .. } => {
-                let rng = ring::rand::SystemRandom::new();
-                let mut signature = vec![];
-                signature.resize(keypair.public_modulus_len(), 0);
-                keypair
-                    .sign(
-                        &ring::signature::RSA_PKCS1_SHA256,
-                        &rng,
-                        data,
-                        signature.as_mut_slice(),
-                    )
-                    .map_err(|_| KeyError::Plain("failed to generate RSA signature!"))?;
-                Ok(signature)
-            }
-        }
-    }
-
-    pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result<bool, UIDCError> {
-        use ring::signature::VerificationAlgorithm;
-        match self {
-            Self::HMAC { .. } => Ok(ring::constant_time::verify_slices_are_equal(
-                self.generate_signature(data)?.as_slice(),
-                signature,
-            )
-            .is_ok()),
-            Self::Ed25519 { keypair, .. } => Ok(ring::signature::ED25519
-                .verify(
-                    keypair.public_key().as_ref().into(),
-                    data.into(),
-                    signature.into(),
-                )
-                .is_ok()),
-            Self::RSA { keypair, .. } => Ok(ring::signature::RSA_PKCS1_2048_8192_SHA256
-                .verify(
-                    keypair.public_key().as_ref().into(),
-                    data.into(),
-                    signature.into(),
-                )
-                .is_ok()),
-        }
-    }
-
-    pub fn parse_from(key: &schema::Key) -> Result<Self, UIDCError> {
-        match key.key_type.as_ref() {
-            KeyType::HMac(hmty) => Ok(ParsedKey::HMAC {
-                key_id: key.key_id.clone(),
-                keydata: key.secret_data.clone(),
-                hmty: *hmty,
-            }),
-            KeyType::RSA2048 | KeyType::RSA4096 => Ok(ParsedKey::RSA {
-                key_id: key.key_id.clone(),
-                keypair: ring::signature::RsaKeyPair::from_pkcs8(&key.secret_data)
-                    .map_err(|_| UIDCError::Abort("could not load RSA key from database"))?,
-            }),
-            KeyType::Ed25519 => Ok(ParsedKey::Ed25519 {
-                key_id: key.key_id.clone(),
-                keypair: Ed25519KeyPair::from_pkcs8(&key.secret_data)
-                    .map_err(|_| UIDCError::Abort("could not load ed25519 key from database"))?,
-            }),
-        }
-    }
-}
-
-pub struct RealmKeys {
-    realm: schema::Realm,
-    // key cache, to avoid reparsing the key data repeatedly
-    keys: RefCell<Vec<(KeyType, Arc<ParsedKey>)>>,
-}
-
-impl RealmKeys {
-    pub fn new(realm: schema::Realm) -> Self {
-        Self {
-            realm,
-            keys: vec![].into(),
-        }
-    }
-
-    pub fn by_key_id(&mut self, id: &String) -> Result<Option<Arc<ParsedKey>>, UIDCError> {
-        // check the cache
-        for key in self.keys.borrow().iter() {
-            if key.1.key_id() == id {
-                return Ok(Some(key.1.clone()));
-            }
-        }
-
-        // then check the database
-        let key = self.realm.keys.with(schema::Key::KeyId, id).first().get()?;
-
-        if let Some(key) = key {
-            let parsed = Arc::new(ParsedKey::parse_from(key.as_ref())?);
-
-            self.keys
-                .borrow_mut()
-                .push((*key.key_type.as_ref(), parsed.clone()));
-
-            Ok(Some(parsed))
-        } else {
-            Ok(None)
-        }
-    }
-
-    pub fn by_type(&mut self, kty: KeyType) -> Result<Option<Arc<ParsedKey>>, UIDCError> {
-        // check the cache
-        for key in self.keys.borrow().iter() {
-            if key.0 == kty {
-                return Ok(Some(key.1.clone()));
-            }
-        }
-
-        // then check the database
-        let key = self
-            .realm
-            .keys
-            .with(schema::Key::KeyType, &kty.into())
-            .first()
-            .get()?;
-
-        if let Some(key) = key {
-            let parsed = Arc::new(ParsedKey::parse_from(key.as_ref())?);
-            self.keys.borrow_mut().push((kty, parsed.clone()));
-
-            Ok(Some(parsed))
-        } else {
-            Ok(None)
+            KeyType::Ed25519 => {
+                jwk::Jwk {
+                    common: jwk::CommonParameters {
+                        public_key_use: Some(jwk::PublicKeyUse::Signature),
+                        key_algorithm: Some(jwk::KeyAlgorithm::EdDSA),
+                        key_id: Some(self.key_id),
+                        ..Default::default()
+                    },
+                    algorithm: jwk::AlgorithmParameters::OctetKeyPair(jwk::OctetKeyPairParameters {
+                        key_type: jwk::OctetKeyPairType::OctetKeyPair,
+                        curve: jwk::EllipticCurve::Ed25519,
+                        x: base64::encode(self.public_data),
+                    })
+                }
+            },
+            _ => todo!()
         }
     }
 }
@@ -248,7 +124,7 @@ fn pubkey_id(data: &[u8]) -> String {
     key_id
 }
 
-fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<ParsedKey, UIDCError> {
+fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<String, UIDCError> {
     // ring does not generate RSA keypairs, so we need to shell out to openssl for this.
     let openssl_output = std::process::Command::new("sh")
         .arg("-c")
@@ -257,9 +133,6 @@ fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<Pars
              -algorithm RSA \
              -pkeyopt rsa_keygen_pubexp:65537 \
              -pkeyopt rsa_keygen_bits:{bits} \
-             | openssl pkcs8 \
-             -topk8 \
-             -nocrypt \
              -outform der"
         ))
         .output()
@@ -267,25 +140,30 @@ fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<Pars
 
     let secret = openssl_output.stdout;
 
-    let keypair = ring::signature::RsaKeyPair::from_pkcs8(&secret)
+    let keypair = ring::signature::RsaKeyPair::from_der(&secret)
         .map_err(|_| UIDCError::Abort("couldn't parse PKCS#8 output from openssl"))?;
     let public = keypair.public_key().as_ref();
     let key_id = pubkey_id(public.as_ref());
     let expiry = time::OffsetDateTime::now_utc() + time::Duration::days(730);
 
+    let public_data = bincode::serialize(&RsaPublicData {
+            modulus: keypair.public_key().modulus().big_endian_without_leading_zero().into(),
+            exponent: keypair.public_key().exponent().big_endian_without_leading_zero().into(),
+    }).unwrap();
+
     realm.keys.insert(schema::Key {
         key_id: key_id.clone(),
         key_type: kty.into(),
         key_state: schema::KeyState::Active.into(),
-        public_data: public.into(),
+        public_data,
         secret_data: secret.into(),
         expiry,
     })?;
 
-    Ok(ParsedKey::RSA { key_id, keypair })
+    Ok(key_id)
 }
 
-pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UIDCError> {
+pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<String, UIDCError> {
     let mut rng = ring::rand::SystemRandom::new();
     match kty {
         KeyType::HMac(hmty) => {
@@ -305,17 +183,12 @@ pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UID
                 key_id: key_id.clone(),
                 key_type: kty.into(),
                 key_state: schema::KeyState::Active.into(),
-                // no separate public data for EdDSA keys
+                // no separate public data for HMAC keys
                 public_data: vec![],
                 secret_data: keydata.clone(),
                 expiry,
             })?;
-
-            Ok(ParsedKey::HMAC {
-                key_id,
-                keydata,
-                hmty,
-            })
+            Ok(key_id)
         }
         KeyType::RSA2048 => generate_rsa(realm, KeyType::RSA2048, 2048),
         KeyType::RSA4096 => generate_rsa(realm, KeyType::RSA4096, 4096),
@@ -335,16 +208,12 @@ pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UID
                 key_id: key_id.clone(),
                 key_type: kty.into(),
                 key_state: schema::KeyState::Active.into(),
-                // no separate public data for EdDSA keys
-                public_data: vec![],
+                public_data: pubkey.as_ref().into(),
                 secret_data: keydata,
                 expiry,
             })?;
 
-            Ok(ParsedKey::Ed25519 {
-                key_id,
-                keypair: loaded_key,
-            })
+            Ok(key_id)
         }
     }
 }

+ 1 - 3
src/main.rs

@@ -4,13 +4,11 @@ mod cli;
 mod client_management;
 mod config;
 mod error;
-mod jwt;
 mod key;
+mod realm;
 mod role_management;
 mod schema;
 mod server;
-mod token;
-mod token_management;
 mod user;
 
 pub use error::UIDCError;

+ 248 - 0
src/realm.rs

@@ -0,0 +1,248 @@
+use std::{
+    collections::{HashMap, HashSet},
+    sync::{Arc, RwLock},
+};
+
+use microrm::prelude::*;
+
+use crate::{
+    config::Config,
+    key::{HMacType, KeyType},
+    schema, UIDCError,
+};
+
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct AccessTokenClaims<'l> {
+    pub sub: &'l str,
+    pub iss: &'l str,
+    pub aud: &'l str,
+    pub iat: u64,
+    pub exp: u64,
+    pub scopes: Vec<&'l str>,
+    pub roles: Vec<String>,
+}
+
+pub struct RealmCache {
+    config: Config,
+    db: schema::UIDCDatabase,
+    realms: RwLock<HashMap<schema::RealmID, Arc<RealmHelper>>>,
+}
+
+impl RealmCache {
+    pub fn new(config: Config, db: schema::UIDCDatabase) -> Self {
+        Self {
+            config,
+            db,
+            realms: Default::default(),
+        }
+    }
+
+    pub fn get_helper(&self, id: schema::RealmID) -> Option<Arc<RealmHelper>> {
+        if let Some(rh) = self.realms.read().unwrap().get(&id) {
+            return Some(rh.clone())
+        }
+
+        let realm = self.db.realms.by_id(id).ok().flatten()?;
+
+        self.realms.write().unwrap().insert(id, RealmHelper::new(self.config.clone(), realm).into());
+        self.get_helper(id)
+    }
+}
+
+pub struct RealmHelper {
+    config: Config,
+    realm: microrm::Stored<schema::Realm>,
+    // cached for later
+    issuer: String,
+
+    encoding_key_cache: RwLock<HashMap<schema::KeyID, jsonwebtoken::EncodingKey>>,
+    decoding_key_cache: RwLock<HashMap<schema::KeyID, jsonwebtoken::DecodingKey>>,
+}
+
+impl RealmHelper {
+    pub fn new(config: Config, realm: microrm::Stored<schema::Realm>) -> Self {
+        Self {
+            issuer: format!("{}/{}", config.base_url, realm.shortname),
+            config,
+            realm,
+
+            encoding_key_cache: Default::default(),
+            decoding_key_cache: Default::default(),
+        }
+    }
+
+    fn determine_roles<'a>(
+        &self,
+        client: &microrm::Stored<schema::Client>,
+        user: &microrm::Stored<schema::User>,
+        scopes: impl Iterator<Item = &'a str>,
+    ) -> Result<Vec<String>, UIDCError> {
+        let mut requested_roles = vec![];
+
+        for scope_name in scopes {
+            if scope_name.len() == 0 {
+                continue;
+            }
+            let Some(scope) = client.scopes.keyed((self.realm.id(), scope_name)).get()? else {
+                // requested nonexistent scope
+                return Err(UIDCError::Abort("requested nonexistent scope!"));
+            };
+            requested_roles.extend(scope.roles.get_ids()?.into_iter());
+        }
+
+        let mut available_roles = vec![]; // HashSet::<schema::RoleID>::new();
+
+        for group in user.groups.get()? {
+            available_roles.extend(group.roles.get_ids()?.into_iter());
+        }
+
+        requested_roles.sort();
+        requested_roles.dedup();
+        available_roles.sort();
+        available_roles.dedup();
+
+        let roles = requested_roles
+            .into_iter()
+            .filter(|x| available_roles.contains(x))
+            .flat_map(|v| {
+                self.realm
+                    .roles
+                    .with(schema::RoleID::default(), v)
+                    .first()
+                    .get()
+            })
+            .flatten()
+            .map(|v| v.wrapped().shortname)
+            .collect();
+
+        Ok(roles)
+    }
+
+    fn with_encoding_key<R>(&self, key: &microrm::Stored<schema::Key>, f: impl FnOnce(&jsonwebtoken::EncodingKey) -> R) -> R {
+        // check to see if the cache will work
+        if let Some(v) = self.encoding_key_cache.read().unwrap().get(&key.id()) {
+            return f(v)
+        }
+
+        // parse an EncodingKey out of the stored key
+        let ekey = match key.key_type.as_ref() {
+            KeyType::HMac(_) => {
+                jsonwebtoken::EncodingKey::from_secret(&key.secret_data)
+            },
+            KeyType::RSA2048 | KeyType::RSA4096 => {
+                jsonwebtoken::EncodingKey::from_rsa_der(&key.secret_data)
+            },
+            KeyType::Ed25519 => {
+                jsonwebtoken::EncodingKey::from_ed_der(&key.secret_data)
+            },
+        };
+
+        f(self.encoding_key_cache.write().unwrap().entry(key.id()).or_insert(ekey))
+    }
+
+    pub fn generate_access_token<'a>(
+        &self,
+        client: &microrm::Stored<schema::Client>,
+        user: &microrm::Stored<schema::User>,
+        scopes: impl Iterator<Item = &'a str> + Clone,
+    ) -> Result<String, UIDCError> {
+        let iat = std::time::SystemTime::now();
+        let exp = iat + std::time::Duration::from_secs(self.config.auth_token_expiry);
+
+        let resulting_roles = self.determine_roles(&client, &user, scopes.clone())?;
+
+        let atclaims = AccessTokenClaims {
+            sub: user.username.as_str(),
+            iss: self.issuer.as_str(),
+            aud: client.shortname.as_str(),
+            iat: iat.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
+            exp: exp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
+            scopes: scopes.collect(),
+            roles: resulting_roles,
+        };
+
+        // find an active key that matches the required key type
+        let Some(ekey) = self
+            .realm
+            .keys
+            .with(
+                schema::Key::KeyState,
+                schema::KeyState::Active.into_serialized(),
+            )
+            .with(schema::Key::KeyType, &client.access_key_type)
+            .first()
+            .get()?
+        else {
+            return Err(UIDCError::Abort("no matching signing key"));
+        };
+
+        let mut hdr = jsonwebtoken::Header::new(match ekey.key_type.as_ref() {
+            KeyType::Ed25519 => jsonwebtoken::Algorithm::EdDSA,
+            KeyType::RSA2048 | KeyType::RSA4096 => jsonwebtoken::Algorithm::RS256,
+            KeyType::HMac(HMacType::Sha256) => Err(UIDCError::Abort("cannot sign access token with HS256"))?,
+            KeyType::HMac(HMacType::Sha512) => Err(UIDCError::Abort("cannot sign access token with HS512"))?,
+        });
+        hdr.kid = Some(ekey.key_id.clone());
+
+        self.with_encoding_key(&ekey, |ekey| {
+            jsonwebtoken::encode(&hdr, &atclaims, ekey).map_err(|e| UIDCError::AbortString(format!("failed to sign token: {e}")))
+        })
+    }
+
+    pub fn generate_refresh_token<'a>(
+        &self,
+        client: &microrm::Stored<schema::Client>,
+        user: &microrm::Stored<schema::User>,
+        scopes: impl Iterator<Item = &'a str> + Clone,
+    ) -> Result<String, UIDCError> {
+        let iat = std::time::SystemTime::now();
+        let exp = iat + std::time::Duration::from_secs(self.config.refresh_token_expiry);
+
+        let resulting_roles = self.determine_roles(&client, &user, scopes.clone())?;
+
+        let atclaims = AccessTokenClaims {
+            sub: user.username.as_str(),
+            iss: self.issuer.as_str(),
+            aud: client.shortname.as_str(),
+            iat: iat.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
+            exp: exp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
+            scopes: scopes.collect(),
+            roles: resulting_roles,
+        };
+
+        // find an active key that matches the required key type
+        let Some(ekey) = self
+            .realm
+            .keys
+            .with(
+                schema::Key::KeyState,
+                schema::KeyState::Active.into_serialized(),
+            )
+            .with(schema::Key::KeyType, &client.refresh_key_type)
+            .first()
+            .get()?
+        else {
+            return Err(UIDCError::Abort("no matching signing key"));
+        };
+
+        let hdr = jsonwebtoken::Header::new(match ekey.key_type.as_ref() {
+            KeyType::Ed25519 => jsonwebtoken::Algorithm::EdDSA,
+            KeyType::RSA2048 | KeyType::RSA4096 => jsonwebtoken::Algorithm::RS256,
+            KeyType::HMac(HMacType::Sha256) => {
+                jsonwebtoken::Algorithm::HS256
+            }
+            KeyType::HMac(HMacType::Sha512) => {
+                jsonwebtoken::Algorithm::HS512
+            }
+        });
+
+        self.with_encoding_key(&ekey, |ekey| {
+            jsonwebtoken::encode(&hdr, &atclaims, &ekey)
+                .map_err(|_| UIDCError::Abort("failed to sign token"))
+        })
+    }
+
+    pub fn trade_refresh_token(&self, rtoken: &str) -> Result<(String, String), UIDCError> {
+        todo!()
+    }
+}

+ 3 - 3
src/schema.rs

@@ -91,7 +91,7 @@ pub struct Realm {
     pub auth_codes: microrm::RelationMap<AuthCode>,
 }
 
-#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Debug)]
+#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
 pub enum KeyState {
     /// Key can be used without restrictions for signing and verification.
     Active,
@@ -99,7 +99,7 @@ pub enum KeyState {
     /// good for verification of existing tokens.
     Retiring,
     /// Key is now fully retired and will not be used for signing or for verification.
-    Retired
+    Retired,
 }
 
 #[derive(Entity)]
@@ -195,7 +195,7 @@ pub struct Scope {
     pub roles: microrm::RelationMap<Role>,
 }
 
-#[derive(Database)]
+#[derive(Clone, Database)]
 pub struct UIDCDatabase {
     pub persistent_config: microrm::IDMap<PersistentConfig>,
 

+ 8 - 1
src/server.rs

@@ -1,4 +1,9 @@
-use crate::{config, schema, UIDCError};
+use std::{
+    collections::HashMap,
+    sync::{Arc, Mutex, RwLock},
+};
+
+use crate::{config, key, schema, UIDCError, realm};
 
 mod oidc;
 mod session;
@@ -8,6 +13,7 @@ pub struct ServerState {
     config: config::Config,
     db: schema::UIDCDatabase,
     templates: handlebars::Handlebars<'static>,
+    realms: realm::RealmCache,
 }
 
 #[derive(Clone)]
@@ -54,6 +60,7 @@ pub async fn run_server(
     port: u16,
 ) -> Result<(), UIDCError> {
     let core_state = Box::leak(Box::new(ServerState {
+        realms: realm::RealmCache::new(config.clone(), db.clone()),
         config,
         db,
         templates: handlebars::Handlebars::new(),

+ 43 - 39
src/server/oidc.rs

@@ -1,6 +1,5 @@
-use crate::{config, key, schema, token, UIDCError};
+use crate::{config, key, schema, UIDCError};
 use microrm::prelude::*;
-use ring::signature::KeyPair;
 use serde::{Deserialize, Serialize};
 
 use super::session::SessionHelper;
@@ -123,26 +122,6 @@ fn do_code_authorize<'l, 's>(
     .into())
 }
 
-fn do_token_authorize<'l, 's>(
-    config: &config::Config,
-    realm: &schema::Realm,
-    client: &schema::Client,
-    user: &schema::User,
-    scopes: impl Iterator<Item = &'l str> + Clone,
-    state: Option<&'s str>,
-) -> Result<tide::Response, OIDCError<'s>> {
-    let token = token::generate_access_token(config, &realm, client, user, scopes.into_iter());
-
-    // TODO: use api::TokenResponse here
-    let response_body = serde_json::json!({
-        "token": token.map_err(|e| OIDCError(OIDCErrorType::ServerError, format!("error while generating token: {:?}", e), state))?,
-    });
-    Ok(tide::Response::builder(200)
-        .content_type(tide::http::mime::JSON)
-        .body(response_body)
-        .build())
-}
-
 fn do_authorize(request: Request, state: Option<&str>) -> Result<tide::Response, OIDCError> {
     let shelper = SessionHelper::new(&request);
     let realm = shelper.get_realm().map_err(|_| {
@@ -227,14 +206,19 @@ fn do_authorize(request: Request, state: Option<&str>) -> Result<tide::Response,
             state,
         )
     } else if qp.response_type == "token" {
-        do_token_authorize(
-            &request.state().core.config,
-            &realm,
-            &client,
-            &user,
-            scopes,
-            state,
-        )
+        let rhelper = request.state().core.realms.get_helper(realm.id()).unwrap();
+
+        let token = rhelper.generate_access_token(&client, &user, scopes).map_err(|e| OIDCError(OIDCErrorType::ServerError, format!("could not generate token: {e}"), state))?;
+
+        Ok(tide::Response::builder(200)
+           .content_type(tide::http::mime::JSON)
+           .body(serde_json::to_vec(&api::TokenResponse {
+                token_type: "bearer",
+                access_token: token.as_str(),
+                refresh_token: None,
+                scope: None,
+           }).unwrap())
+           .build())
     } else {
         Err(OIDCError(
             OIDCErrorType::UnsupportedResponseType,
@@ -287,6 +271,8 @@ async fn do_token<'l>(mut request: Request) -> Result<tide::Response, OIDCError<
             None,
         ))?;
 
+    let rhelper = request.state().core.realms.get_helper(realm.id()).unwrap();
+
     if treq.grant_type == "authorization_code" {
         let Some(code) = treq.code else { todo!() };
         let code = realm
@@ -319,9 +305,7 @@ async fn do_token<'l>(mut request: Request) -> Result<tide::Response, OIDCError<
                 None,
             ))?;
 
-        let access_token = token::generate_access_token(
-            &request.state().core.config,
-            &realm,
+        let access_token = rhelper.generate_access_token(
             &client,
             &user,
             code.scopes.as_ref().iter().map(String::as_str),
@@ -348,10 +332,13 @@ async fn do_token<'l>(mut request: Request) -> Result<tide::Response, OIDCError<
             .build())
     } else if treq.grant_type == "refresh_token" {
         let Some(rtoken) = treq.refresh_token else {
-            return Err(OIDCError(OIDCErrorType::InvalidRequest, "no refresh_token given".into(), None))
+            return Err(OIDCError(
+                OIDCErrorType::InvalidRequest,
+                "no refresh_token given".into(),
+                None,
+            ));
         };
-        let Ok((access, refresh)) =
-            token::trade_refresh_token(&request.state().core.config, rtoken.as_str())
+        let Ok((access, refresh)) = rhelper.trade_refresh_token(rtoken.as_str())
         else {
             todo!();
         };
@@ -387,7 +374,23 @@ async fn jwks(request: Request) -> tide::Result<tide::Response> {
     let shelper = SessionHelper::new(&request);
     let realm = shelper.get_realm()?;
 
-    let keyinfo =
+    // build JWK set
+    let mut jwkset = jsonwebtoken::jwk::JwkSet { keys: vec![] };
+
+    for key in realm.keys.get()?.into_iter() {
+        if *key.key_state.as_ref() == schema::KeyState::Retired {
+            continue
+        }
+
+        // skip HMAC keys
+        if let key::KeyType::HMac(_) = *key.key_type.as_ref() {
+            continue
+        }
+
+        jwkset.keys.push(key.wrapped().into_jwk());
+    }
+
+    /*let keyinfo =
         realm
             .keys
             .get()?
@@ -425,11 +428,12 @@ async fn jwks(request: Request) -> tide::Result<tide::Response> {
 
     let jwks_response = serde_json::json!({
         "keys": keyinfo.collect::<Result<Vec<_>, UIDCError>>()?,
-    });
+    });*/
 
     Ok(tide::Response::builder(200)
         .header(tide::http::headers::ACCESS_CONTROL_ALLOW_ORIGIN, "*")
-        .body(jwks_response)
+        .content_type(tide::http::mime::JSON)
+        .body(serde_json::to_vec(&jwkset).unwrap())
         .build())
 }
 

+ 0 - 122
src/token.rs

@@ -1,122 +0,0 @@
-use crate::{config, jwt, key, schema, UIDCError};
-use microrm::prelude::*;
-
-#[derive(Debug)]
-pub enum TokenError {
-    DBError(microrm::Error),
-    InternalError(&'static str),
-}
-
-impl From<microrm::Error> for TokenError {
-    fn from(value: microrm::Error) -> Self {
-        Self::DBError(value)
-    }
-}
-
-impl From<ring::error::KeyRejected> for TokenError {
-    fn from(_value: ring::error::KeyRejected) -> Self {
-        Self::InternalError("could not load realm key")
-    }
-}
-
-fn determine_roles<'a>(
-    realm: &schema::Realm,
-    user: &schema::User,
-    scopes: impl Iterator<Item = &'a str>,
-) -> Result<Vec<String>, UIDCError> {
-    // find all roles the user can possibly have access to
-    let mut user_roles = user.groups.join(schema::Group::Roles).get()?;
-
-    // find all roles requested by the scopes
-    let mut requested_roles = vec![];
-    for scope_name in scopes {
-        if let Some(scope) = realm
-            .scopes
-            .with(schema::Scope::Shortname, scope_name)
-            .first()
-            .get()?
-        {
-            requested_roles.extend(scope.roles.get()?.into_iter());
-        }
-    }
-
-    user_roles.sort_by_key(|k| k.id());
-    user_roles.dedup();
-    requested_roles.sort_by_key(|k| k.id());
-    requested_roles.dedup();
-
-    // find the intersection between requested roles and the ones the user actually has
-    Ok(requested_roles
-        .into_iter()
-        .filter(|req| user_roles.contains(req))
-        .map(|role| role.wrapped().shortname)
-        .collect())
-}
-
-pub fn generate_access_token<'a>(
-    config: &config::Config,
-    realm: &schema::Realm,
-    client: &schema::Client,
-    user: &schema::User,
-    scopes: impl Iterator<Item = &'a str> + Clone,
-) -> Result<String, UIDCError> {
-    let issuer = format!("{}/{}", config.base_url, realm.shortname,);
-
-    let iat = std::time::SystemTime::now();
-    let exp = iat + std::time::Duration::from_secs(config.auth_token_expiry);
-
-    let resulting_roles = determine_roles(realm, user, scopes.clone())?;
-
-    let token = jwt::JWTData {
-        sub: user.username.as_str(),
-        iss: issuer.as_str(),
-        aud: client.shortname.as_str(),
-        iat: iat.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
-        exp: exp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
-        extras: [
-            (
-                "scopes",
-                serde_json::Value::Array(
-                    scopes
-                        .map(|v| serde_json::Value::String(v.into()))
-                        .collect::<Vec<_>>(),
-                ),
-            ),
-            (
-                "roles",
-                serde_json::Value::Array(
-                    resulting_roles
-                        .into_iter()
-                        .map(serde_json::Value::String)
-                        .collect::<Vec<_>>(),
-                ),
-            ),
-        ]
-        .into(),
-    };
-
-    let mut realmkeys = key::RealmKeys::new(realm.clone());
-
-    let key = realmkeys
-        .by_type(*client.access_key_type.as_ref())?
-        .ok_or(UIDCError::Abort("no matching signing key for realm"))?;
-
-    Ok(jwt::JWT::sign(&key, token).into_string())
-}
-
-pub fn generate_refresh_token<'a>(
-    _config: &config::Config,
-    _realm: &schema::Realm,
-    _client: &schema::Client,
-    _user: &schema::User,
-    _scopes: impl Iterator<Item = &'a str>,
-) -> Result<String, UIDCError> {
-    todo!()
-}
-
-pub fn trade_refresh_token<'a>(
-    _config: &config::Config,
-    _token: &'a str,
-) -> Result<(String, String), UIDCError> {
-    todo!()
-}

+ 6 - 1
src/token_management.rs

@@ -1,4 +1,4 @@
-use crate::{config::Config, jwt, key, schema, token, UIDCError};
+use crate::{config::Config, key, schema, UIDCError};
 use microrm::prelude::*;
 
 pub fn create_auth_token(
@@ -62,6 +62,10 @@ pub fn inspect_token(
     realm: &schema::Realm,
     token: Option<&String>,
 ) -> Result<(), UIDCError> {
+
+    todo!()
+
+    /*
     let token = match token {
         Some(token) => token.clone(),
         None => rpassword::prompt_password("Enter token: ").unwrap(),
@@ -117,4 +121,5 @@ pub fn inspect_token(
     }
 
     Ok(())
+    */
 }