split handler families into files

This commit is contained in:
Cadey Ratio 2020-10-30 10:26:56 -04:00
parent df2f2dcd46
commit c99bc1119f
10 changed files with 278 additions and 178 deletions

View File

@ -2,10 +2,11 @@ CREATE TABLE IF NOT EXISTS handlers
( id UUID DEFAULT uuid_generate_v4() NOT NULL
, user_id UUID NOT NULL
, human_name VARCHAR NOT NULL
, current_version VARCHAR NOT NULL
, async_impl BOOLEAN DEFAULT false
, current_version VARCHAR
, async_impl BOOLEAN NOT NULL DEFAULT false
, created_at TIMESTAMP NOT NULL DEFAULT NOW()
, updated_at TIMESTAMP NOT NULL DEFAULT NOW()
, deleted_at TIMESTAMP
, PRIMARY KEY (id)
, CONSTRAINT fk_user_id
FOREIGN KEY (user_id)

61
src/api/handler.rs Normal file
View File

@ -0,0 +1,61 @@
use crate::{models, schema, MainDatabase};
use super::{Error, Result};
use chrono::prelude::*;
use diesel::prelude::*;
use rocket_contrib::{json::Json, uuid::Uuid};
#[instrument(skip(conn))]
#[get("/handler")]
pub fn list(user: models::User, conn: MainDatabase) -> Result<Json<Vec<models::Handler>>> {
use schema::handlers::dsl::*;
Ok(Json(
handlers
.filter(user_id.eq(user.id))
.load::<models::Handler>(&*conn)
.map_err(Error::Database)?,
))
}
#[instrument(skip(conn))]
#[get("/handler/<uuid>")]
pub fn get(
user: models::User,
uuid: Uuid,
conn: MainDatabase,
) -> Result<Json<models::Handler>> {
use schema::handlers::dsl::*;
let uuid = uuid.into_inner();
let handler = handlers
.find(uuid)
.get_result::<models::Handler>(&*conn)
.map_err(Error::Database)?;
if handler.user_id != user.id {
Err(Error::LackPermissions)
} else {
Ok(Json(handler))
}
}
#[instrument(skip(conn))]
#[delete("/handler/<uuid>")]
pub fn delete(user: models::User, uuid: Uuid, conn: MainDatabase) -> Result {
use schema::handlers::dsl::*;
let uuid = uuid.into_inner();
let hdl: models::Handler = handlers
.find(uuid.clone())
.get_result(&*conn)
.map_err(Error::Database)?;
if hdl.user_id != user.id && !user.is_admin {
return Err(Error::LackPermissions);
}
diesel::update(handlers.find(uuid))
.set(deleted_at.eq(Utc::now().naive_utc()))
.get_result::<models::Handler>(&*conn)?;
Ok(())
}

View File

@ -1,79 +1,16 @@
use crate::{jwt, models, schema, MainDatabase};
use chrono::prelude::*;
use crate::{jwt, models, MainDatabase};
use color_eyre::eyre::Report;
use diesel::prelude::*;
use rocket::http::{ContentType, Status};
use rocket::request::{self, FromRequest, Request};
use rocket::response::Responder;
use rocket::Outcome;
use rocket::Response;
use rocket_contrib::{json::Json, uuid::Uuid};
use rocket::{
http::{ContentType, Status},
request::{self, FromRequest, Request},
response::Responder,
Outcome, Response,
};
use std::io::Cursor;
#[tracing::instrument]
#[get("/user/<uuid>")]
pub fn get_user(user: models::User, uuid: Uuid) -> Result<Json<models::User>> {
if uuid != user.id {
return Err(Error::LackPermissions);
}
Ok(Json(user))
}
#[instrument]
#[get("/whoami")]
pub fn whoami(user: models::User) -> Json<models::User> {
Json(user)
}
#[instrument(skip(conn))]
#[get("/token")]
pub fn get_tokens(user: models::User, conn: MainDatabase) -> Result<Json<Vec<models::Token>>> {
use schema::tokens::dsl::*;
Ok(Json(
tokens
.filter(user_id.eq(user.id))
.load::<models::Token>(&*conn)
.map_err(Error::Database)?,
))
}
#[instrument(skip(conn))]
#[delete("/token/<uuid>")]
pub fn delete_token(user: models::User, conn: MainDatabase, uuid: Uuid) -> Result {
use schema::tokens::dsl::*;
let uuid = uuid.into_inner();
let tok: models::Token = tokens
.find(uuid.clone())
.get_result(&*conn)
.map_err(Error::Database)?;
if tok.user_id != user.id && !user.is_admin {
return Err(Error::LackPermissions);
}
diesel::update(tokens.find(uuid))
.set(deleted_at.eq(Utc::now().naive_utc()))
.get_result::<models::Token>(&*conn)?;
Ok(())
}
#[instrument(skip(conn))]
#[post("/token")]
pub fn create_token(user: models::User, conn: MainDatabase) -> Result<String> {
use schema::tokens;
let tok: models::Token = diesel::insert_into(tokens::table)
.values(&models::NewToken {
user_id: user.id.clone(),
})
.get_result(&*conn).map_err(Error::Database)?;
Ok(jwt::make(user.id, tok.id)?)
}
pub mod handler;
pub mod token;
pub mod user;
#[derive(thiserror::Error, Debug)]
pub enum Error {
@ -143,7 +80,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for models::User {
match jwt::verify(tok, conn) {
Err(why) => {
tracing::error!("JWT verification error: {}", why);
error!("JWT verification error: {}", why);
Outcome::Failure((Status::Unauthorized, ()))
}
Ok(user) => Outcome::Success(user),
@ -155,7 +92,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for models::User {
let tok = keys[0].to_string();
match jwt::verify(tok, conn) {
Err(why) => {
tracing::error!("JWT verification error: {}", why);
error!("JWT verification error: {}", why);
Outcome::Failure((Status::Unauthorized, ()))
}
Ok(user) => Outcome::Success(user),

55
src/api/token.rs Normal file
View File

@ -0,0 +1,55 @@
use crate::{jwt, models, schema, MainDatabase};
use super::{Error, Result};
use chrono::prelude::*;
use diesel::prelude::*;
use rocket_contrib::{json::Json, uuid::Uuid};
#[instrument(skip(conn))]
#[get("/token")]
pub fn list(user: models::User, conn: MainDatabase) -> Result<Json<Vec<models::Token>>> {
use schema::tokens::dsl::*;
Ok(Json(
tokens
.filter(user_id.eq(user.id))
.load::<models::Token>(&*conn)
.map_err(Error::Database)?,
))
}
#[instrument(skip(conn))]
#[delete("/token/<uuid>")]
pub fn delete(user: models::User, conn: MainDatabase, uuid: Uuid) -> Result {
use schema::tokens::dsl::*;
let uuid = uuid.into_inner();
let tok: models::Token = tokens
.find(uuid.clone())
.get_result(&*conn)
.map_err(Error::Database)?;
if tok.user_id != user.id && !user.is_admin {
return Err(Error::LackPermissions);
}
diesel::update(tokens.find(uuid))
.set(deleted_at.eq(Utc::now().naive_utc()))
.get_result::<models::Token>(&*conn)?;
Ok(())
}
#[instrument(skip(conn))]
#[post("/token")]
pub fn create(user: models::User, conn: MainDatabase) -> Result<String> {
use schema::tokens;
let tok: models::Token = diesel::insert_into(tokens::table)
.values(&models::NewToken {
user_id: user.id.clone(),
})
.get_result(&*conn)
.map_err(Error::Database)?;
Ok(jwt::make(user.id, tok.id)?)
}

19
src/api/user.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::models;
use super::{Error, Result};
use rocket_contrib::{json::Json, uuid::Uuid};
#[instrument]
#[get("/user/<uuid>")]
pub fn get(user: models::User, uuid: Uuid) -> Result<Json<models::User>> {
if uuid != user.id {
return Err(Error::LackPermissions);
}
Ok(Json(user))
}
#[instrument]
#[get("/whoami")]
pub fn whoami(user: models::User) -> Json<models::User> {
Json(user)
}

View File

@ -1,4 +1,11 @@
use crate::{MainDatabase, Gitea, models, jwt, schema, api};
use serde::{Deserialize, Serialize};
use diesel::prelude::*;
use rocket::{
http::{Cookie, Cookies, SameSite},
response::Redirect,
};
use rocket_oauth2::{OAuth2, TokenResponse};
/// A user.
/// https://try.gitea.io/api/swagger#model-User
@ -25,3 +32,87 @@ pub fn user(token: String) -> std::io::Result<User> {
let user: User = resp.into_json_deserialize()?;
Ok(user)
}
#[instrument(skip(oauth2, cookies))]
#[get("/login/gitea")]
pub fn login(oauth2: OAuth2<Gitea>, mut cookies: Cookies<'_>) -> Redirect {
oauth2.get_redirect(&mut cookies, &[""]).unwrap()
}
#[instrument(skip(conn, token, cookies))]
#[get("/auth/gitea")]
pub fn callback(
conn: MainDatabase,
token: TokenResponse<Gitea>,
mut cookies: Cookies<'_>,
) -> api::Result<String> {
let tok = token.access_token().to_string();
let refresh = token.refresh_token().unwrap().to_string();
let gitea_user =
user(tok.clone()).map_err(|why| api::Error::ExternalDependencyFailed(why.into()))?;
use schema::{
gitea_tokens, tokens,
users::{
dsl::{email, users},
table as users_table,
},
};
let u: Vec<models::User> = users
.filter(email.eq(gitea_user.email.clone()))
.limit(1)
.load::<models::User>(&*conn)
.map_err(api::Error::Database)?;
let user = if u.len() == 0 {
let u = models::NewUser {
salutation: gitea_user.full_name,
email: gitea_user.email,
is_admin: gitea_user.is_admin,
is_locked: false,
tier: 0,
};
let u: models::User = diesel::insert_into(users_table)
.values(&u)
.get_result(&*conn)
.map_err(api::Error::Database)?;
let tok = models::NewGiteaToken {
user_id: u.id.clone(),
access_token: tok,
refresh_token: refresh,
};
let _: models::GiteaToken = diesel::insert_into(gitea_tokens::table)
.values(&tok)
.get_result(&*conn)
.map_err(api::Error::Database)?;
info!("new account created for {:?}", u);
u
} else {
info!("{} {:?} logged in", u[0].id, u[0].salutation);
u[0].clone()
};
let tok: models::Token = diesel::insert_into(tokens::table)
.values(&models::NewToken {
user_id: user.id.clone(),
})
.get_result(&*conn)
.map_err(api::Error::Database)?;
info!("created new token for {} with id {}", user.id, tok.id);
let tok = jwt::make(user.id, tok.id).map_err(api::Error::InternalServerError)?;
cookies.add_private(
Cookie::build("token", tok.clone())
.same_site(SameSite::Lax)
.finish(),
);
Ok(tok)
}

View File

@ -15,7 +15,7 @@ lazy_static! {
.to_string();
}
#[tracing::instrument]
#[instrument]
pub fn make(user_id: uuid::Uuid, token_id: uuid::Uuid) -> Result<String> {
let key: Hmac<Sha256> = Hmac::new_varkey(&*SECRET.as_bytes()).unwrap();
let mut claims = BTreeMap::new();
@ -27,7 +27,7 @@ pub fn make(user_id: uuid::Uuid, token_id: uuid::Uuid) -> Result<String> {
Ok(token_str)
}
#[tracing::instrument(skip(token, conn))]
#[instrument(skip(token, conn))]
pub fn verify(token: String, conn: MainDatabase) -> Result<models::User> {
use schema::{tokens::dsl::tokens, users::dsl::users};
let key: Hmac<Sha256> = Hmac::new_varkey(&*SECRET.as_bytes()).unwrap();

View File

@ -11,13 +11,8 @@ extern crate tracing;
use color_eyre::eyre::Result;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use rocket::{
http::{Cookie, Cookies, SameSite},
response::Redirect,
};
use rocket_contrib::helmet::SpaceHelmet;
use rocket_oauth2::{OAuth2, TokenResponse};
use rocket_oauth2::{OAuth2};
pub mod api;
pub mod gitea;
@ -30,90 +25,6 @@ pub struct MainDatabase(PgConnection);
pub struct Gitea;
#[instrument(skip(oauth2, cookies))]
#[get("/login/gitea")]
fn gitea_login(oauth2: OAuth2<Gitea>, mut cookies: Cookies<'_>) -> Redirect {
oauth2.get_redirect(&mut cookies, &[""]).unwrap()
}
#[instrument(skip(conn, token, cookies))]
#[get("/auth/gitea")]
fn gitea_callback(
conn: MainDatabase,
token: TokenResponse<Gitea>,
mut cookies: Cookies<'_>,
) -> api::Result<String> {
let tok = token.access_token().to_string();
let refresh = token.refresh_token().unwrap().to_string();
let gitea_user =
gitea::user(tok.clone()).map_err(|why| api::Error::ExternalDependencyFailed(why.into()))?;
use schema::{
gitea_tokens, tokens,
users::{
dsl::{email, users},
table as users_table,
},
};
let u: Vec<models::User> = users
.filter(email.eq(gitea_user.email.clone()))
.limit(1)
.load::<models::User>(&*conn)
.map_err(api::Error::Database)?;
let user = if u.len() == 0 {
let u = models::NewUser {
salutation: gitea_user.full_name,
email: gitea_user.email,
is_admin: gitea_user.is_admin,
is_locked: false,
tier: 0,
};
let u: models::User = diesel::insert_into(users_table)
.values(&u)
.get_result(&*conn)
.map_err(api::Error::Database)?;
let tok = models::NewGiteaToken {
user_id: u.id.clone(),
access_token: tok,
refresh_token: refresh,
};
let _: models::GiteaToken = diesel::insert_into(gitea_tokens::table)
.values(&tok)
.get_result(&*conn)
.map_err(api::Error::Database)?;
info!("new account created for {:?}", u);
u
} else {
info!("{} {:?} logged in", u[0].id, u[0].salutation);
u[0].clone()
};
let tok: models::Token = diesel::insert_into(tokens::table)
.values(&models::NewToken {
user_id: user.id.clone(),
})
.get_result(&*conn)
.map_err(api::Error::Database)?;
info!("created new token for {} with id {}", user.id, tok.id);
let tok = jwt::make(user.id, tok.id).map_err(api::Error::InternalServerError)?;
cookies.add_private(
Cookie::build("token", tok.clone())
.same_site(SameSite::Lax)
.finish(),
);
Ok(tok)
}
fn main() -> Result<()> {
color_eyre::install()?;
tracing_subscriber::fmt::init();
@ -129,14 +40,17 @@ fn main() -> Result<()> {
.mount(
"/api",
routes![
api::whoami,
api::get_user,
api::get_tokens,
api::delete_token,
api::create_token,
api::handler::list,
api::handler::get,
api::handler::delete,
api::user::whoami,
api::user::get,
api::token::list,
api::token::delete,
api::token::create,
],
)
.mount("/", routes![gitea_login, gitea_callback])
.mount("/", routes![gitea::login, gitea::callback])
.launch();
Ok(())

View File

@ -1,4 +1,4 @@
use crate::schema::{gitea_tokens, tokens, users};
use crate::schema::{gitea_tokens, tokens, users, handlers};
use chrono::NaiveDateTime;
use serde::Serialize;
use uuid::Uuid;
@ -57,3 +57,24 @@ pub struct Token {
pub updated_at: NaiveDateTime,
pub deleted_at: Option<NaiveDateTime>,
}
#[derive(Insertable)]
#[table_name = "handlers"]
pub struct NewHandler {
pub user_id: Uuid,
pub human_name: String,
pub current_version: Option<String>,
pub async_impl: bool,
}
#[derive(Queryable, Debug, Clone, Serialize)]
pub struct Handler {
pub id: Uuid,
pub user_id: Uuid,
pub human_name: String,
pub current_version: Option<String>,
pub async_impl: bool,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub deleted_at: Option<NaiveDateTime>,
}

View File

@ -14,10 +14,11 @@ table! {
id -> Uuid,
user_id -> Uuid,
human_name -> Varchar,
current_version -> Varchar,
async_impl -> Nullable<Bool>,
current_version -> Nullable<Varchar>,
async_impl -> Bool,
created_at -> Timestamp,
updated_at -> Timestamp,
deleted_at -> Nullable<Timestamp>,
}
}