Skip to content

Commit

Permalink
feat(homeserver): add signin endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuhvi committed Jul 21, 2024
1 parent c399a8b commit 5a6c7ae
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 35 deletions.
1 change: 1 addition & 0 deletions pubky-homeserver/src/database/tables/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub type UsersTable = Database<PublicKeyCodec, User>;

pub const USERS_TABLE: &str = "users";

// TODO: add more adminstration metadata like quota, invitation links, etc..
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct User {
pub created_at: u64,
Expand Down
1 change: 1 addition & 0 deletions pubky-homeserver/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub fn create_app(state: AppState) -> Router {
.route("/", get(root::handler))
.route("/:pubky", put(auth::signup))
.route("/:pubky/session", get(auth::session))
.route("/:pubky/session", post(auth::signin))
.route("/:pubky/session", delete(auth::signout))
.route("/:pubky/*key", get(drive::put))
.layer(TraceLayer::new_for_http())
Expand Down
86 changes: 51 additions & 35 deletions pubky-homeserver/src/routes/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,41 +34,9 @@ pub async fn signup(
pubky: Pubky,
body: Bytes,
) -> Result<impl IntoResponse> {
let public_key = pubky.public_key();

state.verifier.verify(&body, public_key)?;

let mut wtxn = state.db.env.write_txn()?;
let users: UsersTable = state.db.env.create_database(&mut wtxn, Some(USERS_TABLE))?;

users.put(
&mut wtxn,
public_key,
&User {
created_at: Timestamp::now().into_inner(),
},
)?;

let session_secret = base32::encode(base32::Alphabet::Crockford, &random_bytes::<16>());

let sessions: SessionsTable = state
.db
.env
.open_database(&wtxn, Some(SESSIONS_TABLE))?
.expect("Sessions table already created");

// TODO: handle not having a user agent?
let mut session = Session::new();

session.set_user_agent(user_agent.to_string());

sessions.put(&mut wtxn, &session_secret, &session.serialize())?;

cookies.add(Cookie::new(public_key.to_string(), session_secret));

wtxn.commit()?;

Ok(())
// TODO: Verify invitation link.
// TODO: add errors in case of already axisting user.
signin(State(state), TypedHeader(user_agent), cookies, pubky, body).await
}

pub async fn session(
Expand Down Expand Up @@ -122,3 +90,51 @@ pub async fn signout(

Err(Error::with_status(StatusCode::UNAUTHORIZED))
}

pub async fn signin(
State(state): State<AppState>,
TypedHeader(user_agent): TypedHeader<UserAgent>,
cookies: Cookies,
pubky: Pubky,
body: Bytes,
) -> Result<impl IntoResponse> {
let public_key = pubky.public_key();

state.verifier.verify(&body, public_key)?;

let mut wtxn = state.db.env.write_txn()?;
let users: UsersTable = state.db.env.create_database(&mut wtxn, Some(USERS_TABLE))?;

if let Some(existing) = users.get(&wtxn, public_key)? {
users.put(&mut wtxn, public_key, &existing)?;
} else {
users.put(
&mut wtxn,
public_key,
&User {
created_at: Timestamp::now().into_inner(),
},
)?;
}

let session_secret = base32::encode(base32::Alphabet::Crockford, &random_bytes::<16>());

let sessions: SessionsTable = state
.db
.env
.open_database(&wtxn, Some(SESSIONS_TABLE))?
.expect("Sessions table already created");

// TODO: handle not having a user agent?
let mut session = Session::new();

session.set_user_agent(user_agent.to_string());

sessions.put(&mut wtxn, &session_secret, &session.serialize())?;

cookies.add(Cookie::new(public_key.to_string(), session_secret));

wtxn.commit()?;

Ok(())
}
15 changes: 15 additions & 0 deletions pubky/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ impl PubkyClient {
Ok(())
}

/// Signin to a homeserver.
pub fn signin(&self, keypair: &Keypair) -> Result<()> {
let pubky = keypair.public_key();

let (audience, mut url) = self.resolve_pubky_homeserver(&pubky)?;

url.set_path(&format!("/{}/session", &pubky));

self.request(HttpMethod::Post, &url)
.send_bytes(AuthnSignature::generate(keypair, &audience).as_bytes())
.map_err(Box::new)?;

Ok(())
}

// === Private Methods ===

/// Publish the SVCB record for `_pubky.<public_key>`.
Expand Down
12 changes: 12 additions & 0 deletions pubky/src/client_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,16 @@ impl PubkyClientAsync {

receiver.recv_async().await?
}

/// Async version of [PubkyClient::signin]
pub async fn signin(&self, keypair: &Keypair) -> Result<()> {
let (sender, receiver) = flume::bounded::<Result<()>>(1);

let client = self.0.clone();
let keypair = keypair.clone();

thread::spawn(move || sender.send(client.signin(&keypair)));

receiver.recv_async().await?
}
}
8 changes: 8 additions & 0 deletions pubky/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,13 @@ mod tests {
_ => assert!(false, "expected NotSignedInt error"),
}
}

client.signin(&keypair).await.unwrap();

{
let session = client.session(&keypair.public_key()).await.unwrap();

assert_eq!(session, Session { ..session.clone() });
}
}
}

0 comments on commit 5a6c7ae

Please sign in to comment.