Compare commits
11 commits
main
...
server-end
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c34578786e | ||
|
|
6c32ae8b68 | ||
|
|
d1a223fac9 | ||
|
|
321d36b743 | ||
|
|
b806b28a6e | ||
|
|
d60b70ffb4 | ||
|
|
820f619aeb | ||
|
|
e1fa45e39b | ||
|
|
269a23a8b7 | ||
|
|
82d6eaa2a8 | ||
|
|
9b640a555a |
23 changed files with 1272 additions and 124 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -14,4 +14,5 @@ target/
|
|||
tmp/
|
||||
|
||||
*.db
|
||||
*.sock
|
||||
*.sock
|
||||
*.sqlite3
|
||||
|
|
|
|||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -22,5 +22,10 @@
|
|||
"editor.autoIndent": "advanced",
|
||||
"diffEditor.ignoreTrimWhitespace": false,
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"protoc": {
|
||||
"options": [
|
||||
"--proto_path=proto/"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
881
Cargo.lock
generated
881
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["burrow", "tun"]
|
||||
members = ["burrow", "server", "tun"]
|
||||
resolver = "2"
|
||||
exclude = ["burrow-gtk"]
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ reqwest = { version = "0.12", default-features = false, features = [
|
|||
] }
|
||||
rusqlite = { version = "0.31.0", features = ["blob"] }
|
||||
dotenv = "0.15.0"
|
||||
tonic = "0.12.0"
|
||||
tonic = "0.12.3"
|
||||
prost = "0.13.1"
|
||||
prost-types = "0.13.1"
|
||||
tokio-stream = "0.1"
|
||||
|
|
@ -68,6 +68,9 @@ tower = "0.4.13"
|
|||
hyper-util = "0.1.6"
|
||||
toml = "0.8.15"
|
||||
rust-ini = "0.21.0"
|
||||
jwt-simple = "0.12.10"
|
||||
config = "0.14.1"
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
caps = "0.5"
|
||||
|
|
@ -96,4 +99,4 @@ bundled = ["rusqlite/bundled"]
|
|||
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12.0"
|
||||
tonic-build = "0.12.3"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::compile_protos("../proto/burrow.proto")?;
|
||||
tonic_build::configure().compile_protos(
|
||||
&["../proto/burrow.proto", "../proto/burrowweb.proto"],
|
||||
&["../proto", "../proto"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
pub mod client;
|
||||
pub mod server;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
pub mod slack;
|
||||
pub use super::db;
|
||||
|
||||
#[derive(serde::Deserialize, Default, Debug)]
|
||||
pub struct OpenIdUser {
|
||||
pub sub: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
|
@ -5,18 +5,13 @@ pub mod wireguard;
|
|||
mod daemon;
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
pub mod database;
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
mod auth;
|
||||
|
||||
pub(crate) mod tracing;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use daemon::apple::spawn_in_process;
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
pub use daemon::{
|
||||
rpc::DaemonResponse,
|
||||
rpc::ServerInfo,
|
||||
DaemonClient,
|
||||
DaemonCommand,
|
||||
DaemonResponseData,
|
||||
rpc::DaemonResponse, rpc::ServerInfo, DaemonClient, DaemonCommand, DaemonResponseData,
|
||||
DaemonStartOptions,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ pub(crate) mod tracing;
|
|||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
mod wireguard;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
mod auth;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
use daemon::{DaemonClient, DaemonCommand};
|
||||
|
||||
|
|
@ -52,8 +49,6 @@ enum Commands {
|
|||
ServerConfig,
|
||||
/// Reload Config
|
||||
ReloadConfig(ReloadConfigArgs),
|
||||
/// Authentication server
|
||||
AuthServer,
|
||||
/// Server Status
|
||||
ServerStatus,
|
||||
/// Tunnel Config
|
||||
|
|
@ -276,7 +271,6 @@ async fn main() -> Result<()> {
|
|||
Commands::ServerInfo => try_serverinfo().await?,
|
||||
Commands::ServerConfig => try_serverconfig().await?,
|
||||
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?,
|
||||
Commands::AuthServer => crate::auth::server::serve().await?,
|
||||
Commands::ServerStatus => try_serverstatus().await?,
|
||||
Commands::TunnelConfig => try_tun_config().await?,
|
||||
Commands::NetworkAdd(args) => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package burrow;
|
|||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// Internal service for managing tunnels and networks
|
||||
// Used for IPC
|
||||
|
||||
service Tunnel {
|
||||
rpc TunnelConfiguration (Empty) returns (stream TunnelConfigurationResponse);
|
||||
rpc TunnelStart (Empty) returns (Empty);
|
||||
|
|
|
|||
95
proto/burrowweb.proto
Normal file
95
proto/burrowweb.proto
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package burrowweb;
|
||||
|
||||
// TODO: Frontend sends slack token → receive JWT
|
||||
// TODO: create/delete/list routes
|
||||
|
||||
service BurrowWeb {
|
||||
rpc SlackAuth (SlackAuthRequest) returns (JWTInfo);
|
||||
// Server assigns a IP address, generates a token, saves a user entry,
|
||||
// then responds back with WireGuard configuration
|
||||
rpc CreateDevice (CreateDeviceRequest) returns (CreateDeviceResponse);
|
||||
rpc DeleteDevice (JWTInfo) returns (Empty);
|
||||
rpc ListDevices (JWTInfo) returns (ListDevicesResponse);
|
||||
rpc Status(Empty) returns (ServerStatus);
|
||||
}
|
||||
|
||||
message Peer {
|
||||
string public_key = 1;
|
||||
optional string preshared_key = 2;
|
||||
repeated string allowed_ips = 3;
|
||||
string endpoint = 4;
|
||||
optional uint32 persistent_keepalive = 5;
|
||||
optional string name = 6;
|
||||
}
|
||||
|
||||
message InterfaceConfig {
|
||||
// Does not include private key; the client is responsible for generating & persisting that
|
||||
repeated string address = 1;
|
||||
optional uint32 listen_port = 2;
|
||||
repeated string dns = 3;
|
||||
optional uint32 mtu = 4;
|
||||
}
|
||||
|
||||
message Device {
|
||||
int32 id = 1;
|
||||
optional string name = 2;
|
||||
string public_key = 3;
|
||||
optional string apns_token = 4;
|
||||
int32 user_id = 5;
|
||||
string created_at = 6;
|
||||
string ipv4 = 7;
|
||||
string ipv6 = 8;
|
||||
string access_token = 9;
|
||||
string refresh_token = 10;
|
||||
string expires_at = 11;
|
||||
}
|
||||
|
||||
message User {
|
||||
int32 id = 1;
|
||||
string created_at = 2;
|
||||
}
|
||||
|
||||
message UserConnection {
|
||||
int32 user_id = 1;
|
||||
string openid_provider = 2;
|
||||
string openid_user_id = 3;
|
||||
string openid_user_name = 4;
|
||||
string access_token = 5;
|
||||
string refresh_token = 6;
|
||||
}
|
||||
|
||||
|
||||
message Config {
|
||||
InterfaceConfig interface = 1;
|
||||
repeated Peer peers = 2;
|
||||
}
|
||||
|
||||
|
||||
message Empty {}
|
||||
|
||||
message SlackAuthRequest {
|
||||
string slack_token = 1;
|
||||
}
|
||||
|
||||
message JWTInfo {
|
||||
string jwt = 1;
|
||||
}
|
||||
|
||||
message CreateDeviceRequest {
|
||||
JWTInfo jwt = 1;
|
||||
string public_key = 2; // User's specified WG Public Key
|
||||
}
|
||||
|
||||
message CreateDeviceResponse {
|
||||
Config wg_config = 1;
|
||||
}
|
||||
|
||||
message ListDevicesResponse {
|
||||
repeated Device devices = 1;
|
||||
}
|
||||
|
||||
message ServerStatus {
|
||||
string status = 1;
|
||||
}
|
||||
41
server/Cargo.toml
Normal file
41
server/Cargo.toml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.93"
|
||||
jwt-simple = "0.12.10"
|
||||
log = "0.4.22"
|
||||
reqwest = { version = "0.12.9", default-features = false, features = [
|
||||
"json",
|
||||
"rustls-tls",
|
||||
] }
|
||||
serde = "1.0.215"
|
||||
serde_json = "1.0.133"
|
||||
tokio = { version = "1.41.1", features = [
|
||||
"rt",
|
||||
"macros",
|
||||
"sync",
|
||||
"io-util",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
"time",
|
||||
"tracing",
|
||||
"fs",
|
||||
] }
|
||||
tonic = "0.12.3"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
rusqlite = { version = "0.31.0", features = ["blob"] }
|
||||
dotenvy = "0.15.7"
|
||||
config = "0.14.1"
|
||||
prost = "0.13.3"
|
||||
prost-types = "0.13.3"
|
||||
tonic-web = "0.12.3"
|
||||
|
||||
|
||||
[features]
|
||||
bundled = ["rusqlite/bundled"]
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12.3"
|
||||
4
server/build.rs
Normal file
4
server/build.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::configure().compile_protos(&["../proto/burrowweb.proto"], &["../proto"])?;
|
||||
Ok(())
|
||||
}
|
||||
52
server/src/main.rs
Normal file
52
server/src/main.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
pub mod client;
|
||||
pub mod server;
|
||||
use anyhow::Result;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use server::{providers::gen_keypem, serve};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "Burrow Server")]
|
||||
#[command(author = "Hack Club <team@hackclub.com>")]
|
||||
#[command(version = "0.1")]
|
||||
#[command(
|
||||
about = "Server for hosting auth logic of Burrow",
|
||||
long_about = "Burrow is a 🚀 blazingly fast 🚀 tool designed to penetrate unnecessarily restrictive firewalls, providing teenagers worldwide with secure, less-filtered, and safe access to the internet!
|
||||
It's being built by teenagers from Hack Club, in public! Check it out: https://github.com/hackclub/burrow
|
||||
Spotted a bug? Please open an issue! https://github.com/hackclub/burrow/issues/new"
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
StartServer,
|
||||
#[command(name = "genkeys")]
|
||||
GenKeys(GenKeyArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct GenKeyArgs {
|
||||
#[arg(short, long, default_value = "false")]
|
||||
pub raw: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match &cli.command {
|
||||
Commands::GenKeys(args) => {
|
||||
let pem = gen_keypem();
|
||||
if args.raw {
|
||||
println!(r"{pem:?}");
|
||||
} else {
|
||||
println!("Generated PEM:\n{pem}")
|
||||
}
|
||||
}
|
||||
Commands::StartServer => {
|
||||
serve().await?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::daemon::rpc::grpc_defs::{Network, NetworkType};
|
||||
|
||||
pub static PATH: &str = "./server.sqlite3";
|
||||
|
||||
pub fn init_db() -> Result<()> {
|
||||
|
|
@ -49,7 +47,7 @@ pub fn init_db() -> Result<()> {
|
|||
}
|
||||
|
||||
pub fn store_connection(
|
||||
openid_user: super::providers::OpenIdUser,
|
||||
openid_user: &super::providers::OpenIdUser,
|
||||
openid_provider: &str,
|
||||
access_token: &str,
|
||||
refresh_token: Option<&str>,
|
||||
|
|
@ -84,8 +82,32 @@ pub fn store_device(
|
|||
) -> Result<()> {
|
||||
log::debug!("Storing openid user {:#?}", openid_user);
|
||||
let conn = rusqlite::Connection::open(PATH)?;
|
||||
todo!();
|
||||
conn.execute(
|
||||
"INSERT INTO device (name, public_key, apns_token, user_id, ipv4, ipv6, access_token, refresh_token)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO
|
||||
pub fn delete_device(id: i64) -> Result<()> {
|
||||
let conn = rusqlite::Connection::open(PATH)?;
|
||||
|
||||
conn.execute("DELETE FROM device WHERE id = ?", [id])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_devices(user_id: i64) -> Result<Vec<String>> {
|
||||
let conn = rusqlite::Connection::open(PATH)?;
|
||||
let mut stmt = conn.prepare("SELECT name FROM device WHERE user_id = ?")?;
|
||||
|
||||
let result: Vec<String> = stmt
|
||||
.query_map([user_id], |row| {
|
||||
let name: String = row.get(0)?;
|
||||
Ok(name)
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
5
server/src/server/grpc_defs.rs
Normal file
5
server/src/server/grpc_defs.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub use burrowwebrpc::*;
|
||||
|
||||
pub mod burrowwebrpc {
|
||||
tonic::include_proto!("burrowweb");
|
||||
}
|
||||
82
server/src/server/grpc_server.rs
Normal file
82
server/src/server/grpc_server.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use jwt_simple::prelude::Ed25519KeyPair;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use super::providers::{KeypairT, OpenIdUser};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::{
|
||||
grpc_defs::{
|
||||
burrowwebrpc::burrow_web_server::BurrowWeb, CreateDeviceRequest, CreateDeviceResponse,
|
||||
Empty, JwtInfo, ListDevicesResponse, ServerStatus, SlackAuthRequest,
|
||||
},
|
||||
providers::slack::auth,
|
||||
settings::BurrowAuthServerConfig,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BurrowGrpcServer {
|
||||
config: Arc<BurrowAuthServerConfig>,
|
||||
jwt_keypair: Arc<KeypairT>,
|
||||
}
|
||||
|
||||
impl Debug for BurrowGrpcServer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BurrowGrpcServer")
|
||||
.field("config", &self.config)
|
||||
.field("jwt_keypair", &"<redacted>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BurrowGrpcServer {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let config = BurrowAuthServerConfig::new_dotenv()?;
|
||||
let jwt_keypair = Ed25519KeyPair::from_pem(&config.jwt_pem)?;
|
||||
Ok(Self {
|
||||
config: Arc::new(config),
|
||||
jwt_keypair: Arc::new(jwt_keypair),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl BurrowWeb for BurrowGrpcServer {
|
||||
async fn slack_auth(
|
||||
&self,
|
||||
request: Request<SlackAuthRequest>,
|
||||
) -> Result<Response<JwtInfo>, Status> {
|
||||
auth(request, &self.jwt_keypair).await
|
||||
}
|
||||
|
||||
async fn create_device(
|
||||
&self,
|
||||
request: Request<CreateDeviceRequest>,
|
||||
) -> Result<Response<CreateDeviceResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let jwt = req
|
||||
.jwt
|
||||
.ok_or(Status::invalid_argument("JWT Not existent!"))?;
|
||||
let oid_user = OpenIdUser::try_from_jwt(&jwt, &self.jwt_keypair)
|
||||
.map_err(|e| Status::invalid_argument(e.to_string()))?;
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn delete_device(&self, request: Request<JwtInfo>) -> Result<Response<Empty>, Status> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn list_devices(
|
||||
&self,
|
||||
request: Request<JwtInfo>,
|
||||
) -> Result<Response<ListDevicesResponse>, Status> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn status(&self, _req: Request<Empty>) -> Result<Response<ServerStatus>, Status> {
|
||||
Ok(Response::new(ServerStatus {
|
||||
status: "Nobody expects the Spanish Inquisition".into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,29 @@
|
|||
pub mod db;
|
||||
pub mod grpc_defs;
|
||||
mod grpc_server;
|
||||
pub mod providers;
|
||||
pub mod settings;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{http::StatusCode, routing::post, Router};
|
||||
use providers::slack::auth;
|
||||
use grpc_defs::burrow_web_server::BurrowWebServer;
|
||||
use grpc_server::BurrowGrpcServer;
|
||||
use tokio::signal;
|
||||
use tonic::transport::Server;
|
||||
|
||||
pub async fn serve() -> Result<()> {
|
||||
db::init_db()?;
|
||||
|
||||
let app = Router::new()
|
||||
.route("/slack-auth", post(auth))
|
||||
.route("/device/new", post(device_new));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
|
||||
let addr = "[::1]:8080".parse()?;
|
||||
log::info!("Starting auth server on port 8080");
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let burrow_grpc_server = BurrowGrpcServer::new()?;
|
||||
let svc = BurrowWebServer::new(burrow_grpc_server);
|
||||
Server::builder()
|
||||
.accept_http1(true)
|
||||
.add_service(tonic_web::enable(svc))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn device_new() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
let ctrl_c = async {
|
||||
signal::ctrl_c()
|
||||
76
server/src/server/providers/mod.rs
Normal file
76
server/src/server/providers/mod.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
pub mod slack;
|
||||
use self::grpc_defs::JwtInfo;
|
||||
|
||||
pub use super::{db, grpc_defs, settings::BurrowAuthServerConfig};
|
||||
use anyhow::{anyhow, Result};
|
||||
use jwt_simple::{
|
||||
claims::{Claims, NoCustomClaims},
|
||||
prelude::{Duration, Ed25519KeyPair, EdDSAKeyPairLike, EdDSAPublicKeyLike},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type KeypairT = Ed25519KeyPair;
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct OpenIdUser {
|
||||
pub sub: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct OpenIDCustomField {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl OpenIdUser {
|
||||
pub fn try_from_jwt(jwt_info: &JwtInfo, keypair: &KeypairT) -> Result<Self> {
|
||||
let claims = keypair
|
||||
.public_key()
|
||||
.verify_token::<OpenIDCustomField>(&jwt_info.jwt, None)?;
|
||||
Ok(Self {
|
||||
sub: claims.subject.ok_or(anyhow!("No Subject!"))?,
|
||||
name: claims.custom.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl JwtInfo {
|
||||
fn try_from_oid(oid_user: OpenIdUser, keypair: &KeypairT) -> Result<Self> {
|
||||
let claims = Claims::with_custom_claims(
|
||||
OpenIDCustomField { name: oid_user.name },
|
||||
Duration::from_days(10),
|
||||
)
|
||||
.with_subject(oid_user.sub);
|
||||
let jwt = keypair.sign(claims)?;
|
||||
Ok(Self { jwt })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_keypem() -> String {
|
||||
let keypair = KeypairT::generate();
|
||||
keypair.to_pem()
|
||||
}
|
||||
|
||||
pub fn parse_keypem(pem: &String) -> Result<KeypairT> {
|
||||
let keypair = KeypairT::from_pem(&pem)?;
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_jwt() -> Result<()> {
|
||||
let key_pair = Ed25519KeyPair::generate();
|
||||
let sample_usr = OpenIdUser {
|
||||
sub: "Spanish".into(),
|
||||
name: "Inquisition".into(),
|
||||
};
|
||||
let encoded = JwtInfo::try_from_oid(sample_usr.clone(), &key_pair)?;
|
||||
println!("{}", encoded.jwt);
|
||||
let decoded = OpenIdUser::try_from_jwt(&encoded, &key_pair)?;
|
||||
assert_eq!(decoded, sample_usr);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,26 @@
|
|||
use anyhow::Result;
|
||||
use axum::{
|
||||
extract::Json,
|
||||
http::StatusCode,
|
||||
routing::{get, post},
|
||||
};
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::db::store_connection;
|
||||
use super::grpc_defs::{JwtInfo, SlackAuthRequest};
|
||||
use super::KeypairT;
|
||||
use tonic::{Request as TRequest, Response as TResponse, Result as TResult, Status as TStatus};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SlackToken {
|
||||
slack_token: String,
|
||||
}
|
||||
pub async fn auth(Json(payload): Json<SlackToken>) -> (StatusCode, String) {
|
||||
let slack_user = match fetch_slack_user(&payload.slack_token).await {
|
||||
pub async fn auth(
|
||||
request: TRequest<SlackAuthRequest>,
|
||||
key_pair: &KeypairT,
|
||||
) -> TResult<TResponse<JwtInfo>, TStatus> {
|
||||
let slack_token = request.into_inner().slack_token;
|
||||
let slack_user = match fetch_slack_user(&slack_token).await {
|
||||
Ok(user) => user,
|
||||
Err(e) => {
|
||||
log::error!("Failed to fetch Slack user: {:?}", e);
|
||||
return (StatusCode::UNAUTHORIZED, String::new());
|
||||
return Err(TStatus::unauthenticated("Failed to fetch slack user"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -28,15 +30,18 @@ pub async fn auth(Json(payload): Json<SlackToken>) -> (StatusCode, String) {
|
|||
slack_user.sub
|
||||
);
|
||||
|
||||
let conn = match store_connection(slack_user, "slack", &payload.slack_token, None) {
|
||||
let _conn = match store_connection(&slack_user, "slack", &slack_token, None) {
|
||||
Ok(user) => user,
|
||||
Err(e) => {
|
||||
log::error!("Failed to fetch Slack user: {:?}", e);
|
||||
return (StatusCode::UNAUTHORIZED, String::new());
|
||||
return Err(TStatus::unauthenticated("Failed to store connection"));
|
||||
}
|
||||
};
|
||||
|
||||
(StatusCode::OK, String::new())
|
||||
Ok(TResponse::new(
|
||||
JwtInfo::try_from_oid(slack_user, &key_pair)
|
||||
.map_err(|e| TStatus::unauthenticated(format!("JWT Generation failed: {e}")))?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn fetch_slack_user(access_token: &str) -> Result<super::OpenIdUser> {
|
||||
22
server/src/server/settings.rs
Normal file
22
server/src/server/settings.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use config::{Config, ConfigError, Environment};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BurrowAuthServerConfig {
|
||||
pub jwt_pem: String,
|
||||
}
|
||||
|
||||
impl BurrowAuthServerConfig {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let s = Config::builder()
|
||||
.add_source(Environment::default())
|
||||
.build()?;
|
||||
s.try_deserialize()
|
||||
}
|
||||
|
||||
/// Creates a new config that includes the dotenv
|
||||
pub fn new_dotenv() -> Result<Self, ConfigError> {
|
||||
dotenvy::dotenv().ok();
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue