From 259cad06f8d88db9ddfa85c2c188b5b0130cb393 Mon Sep 17 00:00:00 2001 From: rtkay123 Date: Wed, 30 Jul 2025 08:52:05 +0200 Subject: feat(auth): create keypair --- Cargo.lock | 2 ++ contrib/bruno/users/followers.bru | 4 +-- contrib/docker-compose/init-db/init.sql | 2 +- crates/auth-service/Cargo.toml | 2 ++ .../migrations/20250723100947_user.sql | 1 + crates/auth-service/src/server.rs | 1 + crates/auth-service/src/server/keys.rs | 38 ++++++++++++++++++++++ .../auth-service/src/server/routes/authorised.rs | 12 ++++--- crates/users-service/src/server/manager.rs | 2 +- lib/sellershut-core/proto/users/users.proto | 6 ++-- 10 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 crates/auth-service/src/server/keys.rs diff --git a/Cargo.lock b/Cargo.lock index ec78576..695602b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,7 +2614,9 @@ dependencies = [ "jsonwebtoken", "nanoid", "oauth2", + "rand 0.8.5", "reqwest", + "rsa", "sellershut-core", "serde", "serde_json", diff --git a/contrib/bruno/users/followers.bru b/contrib/bruno/users/followers.bru index 794e286..32523f1 100644 --- a/contrib/bruno/users/followers.bru +++ b/contrib/bruno/users/followers.bru @@ -5,13 +5,13 @@ meta { } get { - url: http://localhost:2210/users/sellershut/followers?cursor=aHR0cDovL2xvY2FsaG9zdDoyMjEwL2FjdGl2aXR5L2ZvbGxvdy9ESmdfZE9oT2h4QUtsOVdESXhXSmp8MjAyNS0wNy0yMFQxMDo1OTozOC41MTgxMTla + url: http://localhost:2210/users/sellershut/followers body: none auth: inherit } params:query { - cursor: aHR0cDovL2xvY2FsaG9zdDoyMjEwL2FjdGl2aXR5L2ZvbGxvdy9ESmdfZE9oT2h4QUtsOVdESXhXSmp8MjAyNS0wNy0yMFQxMDo1OTozOC41MTgxMTla + ~cursor: aHR0cDovL2xvY2FsaG9zdDoyMjEwL2FjdGl2aXR5L2ZvbGxvdy9ESmdfZE9oT2h4QUtsOVdESXhXSmp8MjAyNS0wNy0yMFQxMDo1OTozOC41MTgxMTla } assert { diff --git a/contrib/docker-compose/init-db/init.sql b/contrib/docker-compose/init-db/init.sql index a8b648d..8403f83 100644 --- a/contrib/docker-compose/init-db/init.sql +++ b/contrib/docker-compose/init-db/init.sql @@ -1,3 +1,3 @@ create database sellershut; create database auth; -create database profiles; +create database users; diff --git a/crates/auth-service/Cargo.toml b/crates/auth-service/Cargo.toml index 837fc8b..1cd77fd 100644 --- a/crates/auth-service/Cargo.toml +++ b/crates/auth-service/Cargo.toml @@ -18,7 +18,9 @@ futures-util.workspace = true jsonwebtoken = "9.3.1" nanoid.workspace = true oauth2 = "5.0.0" +rand = "0.8.5" reqwest = { workspace = true, features = ["json", "rustls-tls"] } +rsa = "0.9.8" sellershut-core = { workspace = true, features = ["auth", "serde", "users"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/crates/auth-service/migrations/20250723100947_user.sql b/crates/auth-service/migrations/20250723100947_user.sql index b5566fe..8da2ed2 100644 --- a/crates/auth-service/migrations/20250723100947_user.sql +++ b/crates/auth-service/migrations/20250723100947_user.sql @@ -2,6 +2,7 @@ create table auth_user ( id uuid primary key, email text unique not null, + private_key text not null, updated_at timestamptz not null default now(), created_at timestamptz not null default now() ); diff --git a/crates/auth-service/src/server.rs b/crates/auth-service/src/server.rs index 7b66c42..3433cd2 100644 --- a/crates/auth-service/src/server.rs +++ b/crates/auth-service/src/server.rs @@ -7,6 +7,7 @@ use crate::{ }; pub mod csrf_token_validation; +pub mod keys; pub mod grpc; pub mod routes; diff --git a/crates/auth-service/src/server/keys.rs b/crates/auth-service/src/server/keys.rs new file mode 100644 index 0000000..5c9ee43 --- /dev/null +++ b/crates/auth-service/src/server/keys.rs @@ -0,0 +1,38 @@ +use rsa::{ + pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding}, + RsaPrivateKey, + RsaPublicKey, +}; + +use crate::error::AppError; + +/// A private/public key pair used for HTTP signatures +#[derive(Debug, Clone)] +pub struct Keypair { + /// Private key in PEM format + pub private_key: String, + /// Public key in PEM format + pub public_key: String, +} + +impl Keypair { + /// Helper method to turn this into an openssl private key + #[cfg(test)] + pub(crate) fn private_key(&self) -> Result { + use rsa::pkcs8::DecodePrivateKey; + + Ok(RsaPrivateKey::from_pkcs8_pem(&self.private_key)?) + } +} + +pub fn generate_actor_keypair() -> Result { + let mut rng = rand::thread_rng(); + let rsa = RsaPrivateKey::new(&mut rng, 2048)?; + let pkey = RsaPublicKey::from(&rsa); + let public_key = pkey.to_public_key_pem(LineEnding::default())?; + let private_key = rsa.to_pkcs8_pem(LineEnding::default())?.to_string(); + Ok(Keypair { + private_key, + public_key, + }) +} diff --git a/crates/auth-service/src/server/routes/authorised.rs b/crates/auth-service/src/server/routes/authorised.rs index 2538cdc..b4c2e00 100644 --- a/crates/auth-service/src/server/routes/authorised.rs +++ b/crates/auth-service/src/server/routes/authorised.rs @@ -23,7 +23,7 @@ use crate::{ auth::Claims, error::AppError, server::{ - OAUTH_CSRF_COOKIE, csrf_token_validation::csrf_token_validation_workflow, routes::Provider, + csrf_token_validation::csrf_token_validation_workflow, keys::generate_actor_keypair, routes::Provider, OAUTH_CSRF_COOKIE }, state::AppHandle, }; @@ -49,6 +49,7 @@ struct User { struct DbUser { id: Uuid, email: String, + private_key: String, created_at: OffsetDateTime, updated_at: OffsetDateTime, } @@ -92,8 +93,6 @@ pub async fn login_authorised( .await .context("failed to deserialise response as JSON")?; - dbg!(&user_data); - let user_data: User = serde_json::from_value(user_data)?; if !user_data.verified { @@ -124,19 +123,22 @@ pub async fn login_authorised( .fetch_optional(&mut *transaction) .await?; + let keys = generate_actor_keypair()?; + let user = if let Some(user) = user { user } else { let uuid = uuid::Uuid::now_v7(); let user = sqlx::query_as!( DbUser, - "insert into auth_user (id, email) values ($1, $2) + "insert into auth_user (id, email, private_key) values ($1, $2, $3) on conflict (email) do update set email = excluded.email returning *; ", uuid, user_data.email, + keys.private_key, ) .fetch_one(&mut *transaction) .await?; @@ -180,6 +182,7 @@ pub async fn login_authorised( ), )?; + let user_request = CreateUserRequest { email: user_data.email.to_owned(), avatar: user_data.avatar.as_ref().map(|value| { @@ -188,6 +191,7 @@ pub async fn login_authorised( user_data.id ) }), + public_key: keys.public_key, }; store diff --git a/crates/users-service/src/server/manager.rs b/crates/users-service/src/server/manager.rs index 6738123..05ee4fe 100644 --- a/crates/users-service/src/server/manager.rs +++ b/crates/users-service/src/server/manager.rs @@ -121,7 +121,7 @@ impl UsersService for AppHandle { request.avatar.or(create_user.avatar), request.description, request.user_type.to_string(), - request.public_key, + create_user.public_key, create_user.email, ) .fetch_one(&self.services.postgres) diff --git a/lib/sellershut-core/proto/users/users.proto b/lib/sellershut-core/proto/users/users.proto index 8c7b41d..bbc8188 100644 --- a/lib/sellershut-core/proto/users/users.proto +++ b/lib/sellershut-core/proto/users/users.proto @@ -46,6 +46,8 @@ message CreateUserRequest { string email = 1; // Avatar for the new user optional string avatar = 2; + // Public key + string public_key = 3; } // Response message for CreateUser RPC @@ -70,10 +72,8 @@ message CompleteUserRequest { string outbox = 6; // Is this user local or remote bool local = 7; - // Public key for this user - string public_key = 8; // User type - UserType user_type = 9; + UserType user_type = 8; } // Users gRPC service -- cgit v1.2.3