add paseto middleware

This commit is contained in:
Cadey Ratio 2020-11-03 14:55:03 -05:00
parent 17aa139510
commit b241a3ebd1
6 changed files with 90 additions and 7 deletions

1
backend/Cargo.lock generated
View File

@ -1608,6 +1608,7 @@ dependencies = [
"egg-mode", "egg-mode",
"elefren", "elefren",
"futures-io", "futures-io",
"hex",
"jsonfeed", "jsonfeed",
"kankyo", "kankyo",
"log 0.4.11", "log 0.4.11",

View File

@ -30,6 +30,7 @@ uuid = { version = "0.7", features = ["serde", "v4"] }
rocket_prometheus = "0.7.0" rocket_prometheus = "0.7.0"
prometheus = { version = "0.10", default-features = false, features = ["process"] } prometheus = { version = "0.10", default-features = false, features = ["process"] }
futures-io = "0.3" futures-io = "0.3"
hex = "0.4"
jsonfeed = { git = "https://github.com/Xe/site" } jsonfeed = { git = "https://github.com/Xe/site" }

View File

@ -1,4 +1,4 @@
use crate::{models, schema, web, MainDatabase}; use crate::{models, paseto, schema, web, MainDatabase};
use chrono::prelude::*; use chrono::prelude::*;
use diesel::prelude::*; use diesel::prelude::*;
use rocket::{ use rocket::{
@ -16,7 +16,7 @@ use std::io::Read;
#[get("/members")] #[get("/members")]
#[instrument(skip(conn), err)] #[instrument(skip(conn), err)]
pub fn get_members(conn: MainDatabase) -> Result<Json<Vec<models::Member>>> { pub fn get_members(tok: paseto::Token, conn: MainDatabase) -> Result<Json<Vec<models::Member>>> {
use schema::members; use schema::members;
let results = members::table let results = members::table
.load::<models::Member>(&*conn) .load::<models::Member>(&*conn)
@ -40,6 +40,7 @@ pub fn get_switches(
conn: MainDatabase, conn: MainDatabase,
count: Option<i64>, count: Option<i64>,
page: Option<i64>, page: Option<i64>,
tok: paseto::Token,
) -> Result<Json<Vec<FrontChange>>> { ) -> Result<Json<Vec<FrontChange>>> {
use schema::{members, switches}; use schema::{members, switches};
@ -73,7 +74,7 @@ pub fn get_switches(
#[get("/switches/current")] #[get("/switches/current")]
#[instrument(skip(conn), err)] #[instrument(skip(conn), err)]
pub fn get_current_front(conn: MainDatabase) -> Result<Json<FrontChange>> { pub fn get_current_front(conn: MainDatabase, tok: paseto::Token) -> Result<Json<FrontChange>> {
use schema::{members, switches}; use schema::{members, switches};
let mut front: Vec<(models::Switch, models::Member)> = switches::table let mut front: Vec<(models::Switch, models::Member)> = switches::table
@ -102,6 +103,7 @@ pub fn make_switch(
who: StringBody, who: StringBody,
sc: State<web::switchcounter::Client>, sc: State<web::switchcounter::Client>,
pk: State<web::pluralkit::Client>, pk: State<web::pluralkit::Client>,
tok: paseto::Token,
) -> Result<String> { ) -> Result<String> {
use schema::{members, switches}; use schema::{members, switches};
let who = who.unwrap(); let who = who.unwrap();
@ -159,7 +161,11 @@ pub fn make_switch(
#[get("/switches/<switch_id>")] #[get("/switches/<switch_id>")]
#[instrument(skip(conn), err)] #[instrument(skip(conn), err)]
pub fn get_switch(conn: MainDatabase, switch_id: String) -> Result<Json<FrontChange>> { pub fn get_switch(
tok: paseto::Token,
conn: MainDatabase,
switch_id: String,
) -> Result<Json<FrontChange>> {
use schema::{members, switches::dsl::switches}; use schema::{members, switches::dsl::switches};
let (switch, member): (models::Switch, models::Member) = switches let (switch, member): (models::Switch, models::Member) = switches

View File

@ -20,6 +20,7 @@ pub const APPLICATION_NAME: &str = concat!(
pub mod api; pub mod api;
pub mod models; pub mod models;
pub mod paseto;
pub mod schema; pub mod schema;
pub mod web; pub mod web;

View File

@ -9,7 +9,7 @@ use color_eyre::eyre::Result;
use rocket_contrib::helmet::SpaceHelmet; use rocket_contrib::helmet::SpaceHelmet;
use rocket_prometheus::PrometheusMetrics; use rocket_prometheus::PrometheusMetrics;
use ::mi::{api, web, MainDatabase, APPLICATION_NAME}; use ::mi::{api, paseto, web, MainDatabase, APPLICATION_NAME};
#[get("/.within/botinfo")] #[get("/.within/botinfo")]
fn botinfo() -> &'static str { fn botinfo() -> &'static str {
@ -42,13 +42,14 @@ fn main() -> Result<()> {
.attach(prometheus.clone()) .attach(prometheus.clone())
.attach(MainDatabase::fairing()) .attach(MainDatabase::fairing())
.attach(SpaceHelmet::default()) .attach(SpaceHelmet::default())
.attach(paseto::ed25519_keypair())
.attach(web::pluralkit::Client::fairing()) .attach(web::pluralkit::Client::fairing())
.attach(web::switchcounter::Client::fairing()) .attach(web::switchcounter::Client::fairing())
.mount("/metrics", prometheus) .mount("/metrics", prometheus)
.mount("/", routes![botinfo])
.mount( .mount(
"/", "/api",
routes![ routes![
botinfo,
api::get_members, api::get_members,
api::get_switches, api::get_switches,
api::get_switch, api::get_switch,

73
backend/src/paseto.rs Normal file
View File

@ -0,0 +1,73 @@
use paseto::tokens::{validate_public_token, PasetoPublicKey};
use paseto::PasetoBuilder;
use ring::signature::Ed25519KeyPair;
use rocket::{
fairing::AdHoc,
http::Status,
request::{self, FromRequest, Request},
Outcome, State,
};
use rusty_ulid::generate_ulid_string;
use serde::{Deserialize, Serialize};
pub fn ed25519_keypair() -> AdHoc {
AdHoc::on_attach("ed25519 keypair for paseto", |rocket| {
let cfg = rocket.config();
let table = cfg.get_table("paseto").unwrap();
let private = table["private"].as_str().unwrap().to_string();
let private = hex::decode(&private).unwrap();
let public = table["public"].as_str().unwrap().to_string();
let public = hex::decode(&public).unwrap();
let kp = Ed25519KeyPair::from_seed_and_public_key(&private, &public).unwrap();
let token = PasetoBuilder::new()
.set_ed25519_key(kp)
.set_issued_at(None)
.set_issuer("manual API call".into())
.set_audience("wizards".into())
.set_jti(generate_ulid_string())
.set_subject("Within".into())
.build()
.unwrap();
debug!("token: {}", token);
Ok(rocket
.manage(Ed25519KeyPair::from_seed_and_public_key(&private, &public).unwrap())
.manage(PasetoPublicKey::ED25519KeyPair(
Ed25519KeyPair::from_seed_and_public_key(&private, &public).unwrap(),
)))
})
}
#[derive(Debug, Deserialize)]
pub struct Token {
pub jti: String,
pub sub: String,
pub aud: String,
}
impl<'a, 'r> FromRequest<'a, 'r> for Token {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let keys: Vec<_> = request.headers().get("authorization").collect();
match keys.len() {
1 => {
let tok = keys[0];
let paseto_key = request.guard::<State<PasetoPublicKey>>().unwrap();
match validate_public_token(tok, None, &paseto_key) {
Ok(val) => {
let tok: Token = serde_json::from_value(val).unwrap();
Outcome::Success(tok)
}
Err(why) => {
error!("paseto error: {}", why);
Outcome::Failure((Status::Unauthorized, ()))
}
}
}
_ => Outcome::Failure((Status::Unauthorized, ())),
}
}
}