feat(homeserver): add signin endpoint

This commit is contained in:
nazeh
2024-07-21 11:50:22 +03:00
parent c399a8b3be
commit 5a6c7ae9c5
6 changed files with 88 additions and 35 deletions

View File

@@ -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,

View File

@@ -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())

View File

@@ -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(
@@ -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(())
}

View File

@@ -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>`.

View File

@@ -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?
}
}

View File

@@ -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() });
}
}
}