mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-21 06:54:19 +01:00
feat(homeserver): add signin endpoint
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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>`.
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user