-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
275 additions
and
11 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
//! Mockable UUID generation. | ||
|
||
use ::uuid::Uuid; | ||
use std::sync::atomic::{AtomicUsize, Ordering}; | ||
|
||
/// Handles generation of UUIDs. This is used instead of the uuid crate directly, | ||
/// to better support deterministic UUID creation in tests. | ||
#[derive(Debug)] | ||
pub struct UuidProvider { | ||
#[cfg(not(test))] | ||
provider: ThreadLocalRng, | ||
#[cfg(test)] | ||
provider: Box<dyn UuidProviderT>, | ||
} | ||
|
||
impl UuidProvider { | ||
#[allow(dead_code)] | ||
pub fn new_thread_local() -> Self { | ||
Self { | ||
#[cfg(test)] | ||
provider: Box::new(ThreadLocalRng), | ||
#[cfg(not(test))] | ||
provider: ThreadLocalRng, | ||
} | ||
} | ||
|
||
/// Allows controlling the sequence of generated UUIDs. Only available in | ||
/// `cfg(test)`. | ||
#[allow(dead_code)] | ||
#[cfg(test)] | ||
pub fn new_from_sequence(uuids: Vec<Uuid>) -> Self { | ||
Self { | ||
provider: Box::new(TestSequence::new(uuids)), | ||
} | ||
} | ||
|
||
#[inline] | ||
pub fn next_v4(&self) -> Uuid { | ||
self.provider.next_v4() | ||
} | ||
} | ||
|
||
impl Default for UuidProvider { | ||
fn default() -> Self { | ||
Self::new_thread_local() | ||
} | ||
} | ||
|
||
trait UuidProviderT: std::fmt::Debug + Send + Sync + 'static { | ||
fn next_v4(&self) -> Uuid; | ||
} | ||
|
||
#[derive(Debug)] | ||
struct ThreadLocalRng; | ||
impl UuidProviderT for ThreadLocalRng { | ||
fn next_v4(&self) -> Uuid { | ||
Uuid::new_v4() | ||
} | ||
} | ||
|
||
/// Provides UUIDs from a known sequence. Useful for tests. | ||
#[derive(Debug)] | ||
struct TestSequence { | ||
uuids: Vec<Uuid>, | ||
pos: AtomicUsize, | ||
} | ||
impl TestSequence { | ||
/// # Panics | ||
/// Panics if len of vec is 0 | ||
#[allow(dead_code)] | ||
fn new(uuids: Vec<Uuid>) -> Self { | ||
assert!(!uuids.is_empty()); | ||
Self { | ||
uuids, | ||
pos: AtomicUsize::new(0), | ||
} | ||
} | ||
} | ||
|
||
impl UuidProviderT for TestSequence { | ||
fn next_v4(&self) -> Uuid { | ||
let curr_pos = self.pos.fetch_add(1, Ordering::SeqCst) % self.uuids.len(); | ||
self.uuids[curr_pos] | ||
} | ||
} | ||
|
||
fn _assert_bounds(p: UuidProvider) { | ||
fn helper(_p: impl std::fmt::Debug + Send + Sync + 'static) {} | ||
helper(p) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_sequence_order() { | ||
let uuids: Vec<Uuid> = (0..4).map(|_| Uuid::new_v4()).collect(); | ||
let sequence = TestSequence::new(uuids.clone()); | ||
for uuid in uuids { | ||
assert_eq!(uuid, sequence.next_v4()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,116 @@ | ||
//! V1 of the API. This is subject to change until we commit to stability, after | ||
//! which point any breaking changes will go in a V2 api. | ||
|
||
use axum::{routing::post, Json, Router}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::{collections::BTreeSet, sync::Arc}; | ||
|
||
/// Router of API V1 | ||
pub fn router() -> Router { | ||
Router::new().route("/create", post(create)) | ||
use axum::{ | ||
extract::{Path, State}, | ||
response::Redirect, | ||
routing::{get, post}, | ||
Json, Router, | ||
}; | ||
use did_simple::crypto::ed25519; | ||
use jose_jwk::Jwk; | ||
use uuid::Uuid; | ||
|
||
use crate::uuid::UuidProvider; | ||
|
||
#[derive(Debug)] | ||
struct RouterState { | ||
uuid_provider: UuidProvider, | ||
} | ||
type SharedState = Arc<RouterState>; | ||
|
||
/// Configuration for the V1 api's router. | ||
#[derive(Debug, Default)] | ||
pub struct RouterConfig { | ||
pub uuid_provider: UuidProvider, | ||
} | ||
|
||
impl RouterConfig { | ||
pub fn build(self) -> Router { | ||
Router::new() | ||
.route("/create", post(create)) | ||
.route("/users/:id/did.json", get(read)) | ||
.with_state(Arc::new(RouterState { | ||
uuid_provider: self.uuid_provider, | ||
})) | ||
} | ||
} | ||
|
||
async fn create(state: State<SharedState>, _pubkey: Json<Jwk>) -> Redirect { | ||
let uuid = state.uuid_provider.next_v4(); | ||
Redirect::to(&format!("/users/{}/did.json", uuid.as_hyphenated())) | ||
} | ||
|
||
async fn create(_pubkey: Json<JWK>) -> String { | ||
String::from("did:web:todo") | ||
async fn read(_state: State<SharedState>, Path(_user_id): Path<Uuid>) -> Json<Jwk> { | ||
Json(ed25519_pub_jwk( | ||
ed25519::SigningKey::random().verifying_key(), | ||
)) | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct JWK; | ||
fn ed25519_pub_jwk(pub_key: ed25519::VerifyingKey) -> jose_jwk::Jwk { | ||
Jwk { | ||
key: jose_jwk::Okp { | ||
crv: jose_jwk::OkpCurves::Ed25519, | ||
x: pub_key.into_inner().as_bytes().as_slice().to_owned().into(), | ||
d: None, | ||
} | ||
.into(), | ||
prm: jose_jwk::Parameters { | ||
ops: Some(BTreeSet::from([jose_jwk::Operations::Verify])), | ||
..Default::default() | ||
}, | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use base64::Engine as _; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn pub_jwk_test_vectors() { | ||
// See https://datatracker.ietf.org/doc/html/rfc8037#appendix-A.2 | ||
let rfc_example = serde_json::json! ({ | ||
"kty": "OKP", | ||
"crv": "Ed25519", | ||
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" | ||
}); | ||
let pubkey_bytes = hex_literal::hex!( | ||
"d7 5a 98 01 82 b1 0a b7 d5 4b fe d3 c9 64 07 3a | ||
0e e1 72 f3 da a6 23 25 af 02 1a 68 f7 07 51 1a" | ||
); | ||
assert_eq!( | ||
base64::prelude::BASE64_URL_SAFE_NO_PAD | ||
.decode(rfc_example["x"].as_str().unwrap()) | ||
.unwrap(), | ||
pubkey_bytes, | ||
"sanity check: example bytes should match, they come from the RFC itself" | ||
); | ||
|
||
let input_key = ed25519::VerifyingKey::try_from_bytes(&pubkey_bytes).unwrap(); | ||
let mut output_jwk = ed25519_pub_jwk(input_key); | ||
|
||
// Check all additional outputs for expected values | ||
assert_eq!( | ||
output_jwk.prm.ops.take().unwrap(), | ||
BTreeSet::from([jose_jwk::Operations::Verify]), | ||
"expected Verify as a supported operation" | ||
); | ||
let output_jwk = output_jwk; // Freeze mutation from here on out | ||
|
||
// Check serialization and deserialization against the rfc example | ||
assert_eq!( | ||
serde_json::from_value::<Jwk>(rfc_example.clone()).unwrap(), | ||
output_jwk, | ||
"deserializing json to Jwk did not match" | ||
); | ||
assert_eq!( | ||
rfc_example, | ||
serde_json::to_value(output_jwk).unwrap(), | ||
"serializing Jwk to json did not match" | ||
); | ||
} | ||
} |