Skip to content

Commit

Permalink
Registration & login work end-to-end
Browse files Browse the repository at this point in the history
  • Loading branch information
marcua committed Aug 13, 2023
1 parent 0d4697f commit 735ccd0
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 18 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ Once the server is running, you can set its URL as an environment variable calle
```bash
$ export AYB_SERVER_URL=http://127.0.0.1:5433

$ ayb client register marcua
$ ayb client register marcua you@example.com
Successfully registered marcua

# You will receive an email at you@example.com instructing you to type the next command
$ ayb client confirm TOKEN_FROM_EMAIL

$ ayb client create_database marcua/test.sqlite
Successfully created marcua/test.sqlite

Expand Down Expand Up @@ -82,11 +85,15 @@ Rows: 3

The command line invocations above are a thin wrapper around `ayb`'s HTTP API. Here are the same commands as above, but with `curl`:
```bash
$ curl -w "\n" -X POST http://127.0.0.1:5433/v1/marcua -H "entity-type: user"
$ curl -w "\n" -X POST http://127.0.0.1:5433/v1/register/marcua -H "entity-type: user" -H "email-address: your@example.com"

{}

$ curl -w "\n" -X POST http://127.0.0.1:5433/v1/confirm -H "authentication-token: TOKEN_FROM_EMAIL"

{"entity":"marcua","entity_type":"user"}
{"name":"default","key":"insecure, unimplemented"}

$ curl -w "\n" -X POST http://127.0.0.1:5433/v1/marcua/test.sqlite -H "db-type: sqlite"
$ curl -w "\n" -X POST http://127.0.0.1:5433/v1/marcua/test.sqlite/create -H "db-type: sqlite"

{"entity":"marcua","database":"test.sqlite","database_type":"sqlite"}

Expand Down
2 changes: 1 addition & 1 deletion src/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub async fn send_registration_email(
return send_email(
to,
"Your login credentials",
format!("To log in, type\n\tstacks client confirm {token}"),
format!("To log in, type\n\tayb client confirm {token}"),
config,
)
.await;
Expand Down
23 changes: 22 additions & 1 deletion src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ impl AybClient {
.await
}

pub async fn log_in(&self, entity: &str) -> Result<EmptyResponse, AybError> {
let mut headers = HeaderMap::new();
headers.insert(
HeaderName::from_static("entity"),
HeaderValue::from_str(entity).unwrap(),
);

let response = reqwest::Client::new()
.post(self.make_url("log_in".to_owned()))
.headers(headers)
.send()
.await?;

self.handle_response(response, reqwest::StatusCode::OK)
.await
}

pub async fn query(
&self,
entity: &str,
Expand All @@ -99,6 +116,10 @@ impl AybClient {
entity_type: &EntityType,
) -> Result<EmptyResponse, AybError> {
let mut headers = HeaderMap::new();
headers.insert(
HeaderName::from_static("entity"),
HeaderValue::from_str(entity).unwrap(),
);
headers.insert(
HeaderName::from_static("email-address"),
HeaderValue::from_str(email_address).unwrap(),
Expand All @@ -109,7 +130,7 @@ impl AybClient {
);

let response = reqwest::Client::new()
.post(self.make_url(format!("register/{}", entity)))
.post(self.make_url(format!("register")))
.headers(headers)
.send()
.await?;
Expand Down
50 changes: 45 additions & 5 deletions src/http/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::hosted_db::paths::database_path;
use crate::hosted_db::{run_query, QueryResult};
use crate::http::structs::{
APIKey as APIAPIKey, AuthenticationDetails, AuthenticationMode, AybConfig,
Database as APIDatabase, EmptyResponse, EntityDatabasePath, EntityPath,
Database as APIDatabase, EmptyResponse, EntityDatabasePath,
};
use crate::http::tokens::{decrypt_auth_token, encrypt_auth_token};
use crate::http::utils::get_header;
Expand Down Expand Up @@ -109,6 +109,46 @@ async fn create_database(
Ok(HttpResponse::Created().json(APIDatabase::from_persisted(&entity, &created_database)))
}

#[post("/v1/log_in")]
async fn log_in(
req: HttpRequest,
ayb_db: web::Data<Box<dyn AybDb>>,
ayb_config: web::Data<AybConfig>,
) -> Result<HttpResponse, AybError> {
let entity = get_header(&req, "entity")?;
let desired_entity = ayb_db.get_entity(&entity).await;

if let Ok(instantiated_entity) = desired_entity {
let auth_methods = ayb_db
.list_authentication_methods(&instantiated_entity)
.await?;
for method in auth_methods {
if AuthenticationMethodType::from_i16(method.method_type)
== AuthenticationMethodType::Email
&& AuthenticationMethodStatus::from_i16(method.status)
== AuthenticationMethodStatus::Verified
{
let token = encrypt_auth_token(
&AuthenticationDetails {
version: 1,
mode: AuthenticationMode::Register as i16,
entity: entity,
entity_type: instantiated_entity.entity_type,
email_address: method.email_address.to_owned(),
},
&ayb_config.authentication,
)?;
send_registration_email(&method.email_address, &token, &ayb_config.email).await?;
return Ok(HttpResponse::Ok().json(EmptyResponse {}));
}
}
}

return Err(AybError {
message: format!("No email authentication method for this entity."),
});
}

#[post("/v1/{entity}/{database}/query")]
async fn query(
path: web::Path<EntityDatabasePath>,
Expand All @@ -125,16 +165,16 @@ async fn query(
Ok(web::Json(result))
}

#[post("/v1/register/{entity}")]
#[post("/v1/register")]
async fn register(
path: web::Path<EntityPath>,
req: HttpRequest,
ayb_db: web::Data<Box<dyn AybDb>>,
ayb_config: web::Data<AybConfig>,
) -> Result<HttpResponse, AybError> {
let entity = get_header(&req, "entity")?;
let email_address = get_header(&req, "email-address")?;
let entity_type = get_header(&req, "entity-type")?;
let desired_entity = ayb_db.get_entity(&path.entity).await;
let desired_entity = ayb_db.get_entity(&entity).await;
// Ensure that there are no authentication methods aside from
// perhaps the currently requested one.
let mut already_verified = false;
Expand Down Expand Up @@ -163,7 +203,7 @@ async fn register(
&AuthenticationDetails {
version: 1,
mode: AuthenticationMode::Register as i16,
entity: path.entity.clone(),
entity: entity.clone(),
entity_type: EntityType::from_str(&entity_type) as i16,
email_address: email_address.to_owned(),
},
Expand Down
5 changes: 3 additions & 2 deletions src/http/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::ayb_db::db_interfaces::connect_to_ayb_db;
use crate::http::endpoints::{confirm, create_database, query, register};
use crate::http::endpoints::{confirm, create_database, log_in, query, register};
use crate::http::structs::AybConfig;
use actix_web::{middleware, web, App, HttpServer};
use dyn_clone::clone_box;
Expand All @@ -8,10 +8,11 @@ use std::path::PathBuf;
use toml;

pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(confirm);
cfg.service(create_database);
cfg.service(log_in);
cfg.service(query);
cfg.service(register);
cfg.service(confirm);
}

pub async fn run_server(config_path: &PathBuf) -> std::io::Result<()> {
Expand Down
5 changes: 0 additions & 5 deletions src/http/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ pub struct EntityDatabasePath {
pub database: String,
}

#[derive(Serialize, Deserialize)]
pub struct EntityPath {
pub entity: String,
}

#[derive(
Serialize_repr, Deserialize_repr, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum,
)]
Expand Down
17 changes: 17 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ async fn main() -> std::io::Result<()> {
.arg(arg!(<authentication_token> "The authentication token")
.required(true))
)
.subcommand(
Command::new("log_in")
.about("Log in to ayb via email authentication")
.arg(arg!(<entity> "The entity to log in as")
.required(true))
)
)
.get_matches();

Expand Down Expand Up @@ -170,6 +176,17 @@ async fn main() -> std::io::Result<()> {
}
}
}
} else if let Some(matches) = matches.subcommand_matches("log_in") {
if let Some(entity) = matches.get_one::<String>("entity") {
match client.log_in(entity).await {
Ok(_response) => {
println!("Check your email to finish logging in {}", entity);
}
Err(err) => {
println!("Error: {}", err);
}
}
}
} else if let Some(matches) = matches.subcommand_matches("query") {
if let (Some(entity_database), Some(query), Some(format)) = (
matches.get_one::<EntityDatabasePath>("database"),
Expand Down

0 comments on commit 735ccd0

Please sign in to comment.