update: cloudflare sucks
This commit is contained in:
parent
0fffd33b79
commit
e151a799ef
10 changed files with 566 additions and 1156 deletions
2
.envrc
2
.envrc
|
|
@ -1 +1 @@
|
||||||
use flake . --impure
|
use flake
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
||||||
envfile
|
envfile
|
||||||
.direnv
|
.direnv
|
||||||
.target
|
.target
|
||||||
|
.env
|
||||||
|
|
|
||||||
785
Cargo.lock
generated
785
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,8 +7,11 @@ edition = "2021"
|
||||||
axum = { version = "0.7.5", features = ["multipart", "macros"] }
|
axum = { version = "0.7.5", features = ["multipart", "macros"] }
|
||||||
axum-extra = { version = "0.9.3", features = ["cookie-private"] }
|
axum-extra = { version = "0.9.3", features = ["cookie-private"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
oauth2 = "5.0.0-alpha"
|
color-eyre = "0.6.3"
|
||||||
reqwest = { version = "0.12.5", features = ["json"] }
|
http = "1.1.0"
|
||||||
|
pct-str = "2.0.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
reqwest = { version = "0.12.5", features = ["json", "default-tls"] }
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
serde_json = "1.0.118"
|
serde_json = "1.0.118"
|
||||||
time = "0.3.36"
|
time = "0.3.36"
|
||||||
|
|
|
||||||
254
flake.lock
generated
254
flake.lock
generated
|
|
@ -1,109 +1,15 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"cargo-semver-checks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"naersk": "naersk",
|
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1718446388,
|
|
||||||
"narHash": "sha256-/4a5CS5EdI+g1Y/nzOeAmI04olLDCk8SDhQbPqL82s4=",
|
|
||||||
"owner": "Maix0",
|
|
||||||
"repo": "cargo-semver-checks-flake",
|
|
||||||
"rev": "10641cbb61ecb2ec40c4847080a08c8d4ee8bec0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "Maix0",
|
|
||||||
"repo": "cargo-semver-checks-flake",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cargo-workspace": {
|
|
||||||
"inputs": {
|
|
||||||
"cargo-workspaces-git": "cargo-workspaces-git",
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"naersk": "naersk_2",
|
|
||||||
"nixpkgs": "nixpkgs_4"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1718446390,
|
|
||||||
"narHash": "sha256-YAgg27LQwK6BfOIut/U8zqDN87L0SxU9SRC98Bsp16w=",
|
|
||||||
"owner": "Maix0",
|
|
||||||
"repo": "cargo-ws-flake",
|
|
||||||
"rev": "bbb8a52d1a294549f81a52c226fa3c085e44e5af",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "Maix0",
|
|
||||||
"repo": "cargo-ws-flake",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cargo-workspaces-git": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1683825807,
|
|
||||||
"narHash": "sha256-WlJu8FLaTMzR5JCU5eASHfmJ3C4IdwlPZ6//m5xOb1k=",
|
|
||||||
"owner": "pksunkara",
|
|
||||||
"repo": "cargo-workspaces",
|
|
||||||
"rev": "8bf9c4ab519eeed45e83cbc64aa646431bddd899",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "pksunkara",
|
|
||||||
"ref": "v0.2.41",
|
|
||||||
"repo": "cargo-workspaces",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1726560853,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710146030,
|
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_3": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710146030,
|
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -117,47 +23,11 @@
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717067539,
|
"lastModified": 1721727458,
|
||||||
"narHash": "sha256-oIs5EF+6VpHJRvvpVWuqCYJMMVW/6h59aYUv9lABLtY=",
|
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"rev": "fa19d8c135e776dc97f4dcca08656a0eeb28d5c0",
|
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"naersk_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1717067539,
|
|
||||||
"narHash": "sha256-oIs5EF+6VpHJRvvpVWuqCYJMMVW/6h59aYUv9lABLtY=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"rev": "fa19d8c135e776dc97f4dcca08656a0eeb28d5c0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"naersk_3": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs_5"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1718727675,
|
|
||||||
"narHash": "sha256-uFsCwWYI2pUpt0awahSBorDUrUfBhaAiyz+BPTS2MHk=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"rev": "941ce6dc38762a7cfb90b5add223d584feed299b",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -169,8 +39,8 @@
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 0,
|
"lastModified": 0,
|
||||||
"narHash": "sha256-yZKhxVIKd2lsbOqYd5iDoUIwsRZFqE87smE2Vzf6Ck0=",
|
"narHash": "sha256-o8VBeCWHBxGd4kVMceIayf5GApqTavJbTa44Xcg5Rrk=",
|
||||||
"path": "/nix/store/5jgh89kgmrb687c254wxdac4cj5hqjw8-source",
|
"path": "/nix/store/p2hby44a0qzrnd1vxcpcgfav6160rmcv-source",
|
||||||
"type": "path"
|
"type": "path"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -180,11 +50,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1718276985,
|
"lastModified": 1727335715,
|
||||||
"narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=",
|
"narHash": "sha256-1uw3y94dA4l22LkqHRIsb7qr3rV5XdxQFqctINfx8Cc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3f84a279f1a6290ce154c5531378acc827836fbb",
|
"rev": "28b5b8af91ffd2623e995e20aee56510db49001a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -195,62 +65,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
|
||||||
"lastModified": 0,
|
|
||||||
"narHash": "sha256-yZKhxVIKd2lsbOqYd5iDoUIwsRZFqE87smE2Vzf6Ck0=",
|
|
||||||
"path": "/nix/store/5jgh89kgmrb687c254wxdac4cj5hqjw8-source",
|
|
||||||
"type": "path"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_4": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1718276985,
|
|
||||||
"narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "3f84a279f1a6290ce154c5531378acc827836fbb",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_5": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 0,
|
|
||||||
"narHash": "sha256-4Zu0RYRcAY/VWuu6awwq4opuiD//ahpc2aFHg2CWqFY=",
|
|
||||||
"path": "/nix/store/qqwr649pc0qprc9lw2fmdsi1km6p7q2h-source",
|
|
||||||
"type": "path"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_6": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1719223410,
|
|
||||||
"narHash": "sha256-jtIo8xR0Zp4SalIwmD+OdCwHF4l7OU6PD63UUK4ckt4=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "efb39c6052f3ce51587cf19733f5f4e5d515aa13",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_7": {
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1718428119,
|
"lastModified": 1718428119,
|
||||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
||||||
|
|
@ -268,24 +82,22 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"cargo-semver-checks": "cargo-semver-checks",
|
"flake-utils": "flake-utils",
|
||||||
"cargo-workspace": "cargo-workspace",
|
"naersk": "naersk",
|
||||||
"flake-utils": "flake-utils_3",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"naersk": "naersk_3",
|
|
||||||
"nixpkgs": "nixpkgs_6",
|
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_7"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1719281921,
|
"lastModified": 1727404165,
|
||||||
"narHash": "sha256-LIBMfhM9pMOlEvBI757GOK5l0R58SRi6YpwfYMbf4yc=",
|
"narHash": "sha256-kZCiYpQJBZ3kL9QymS88mCxpQwqo8KqvZeHk6LATuY8=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "b6032d3a404d8a52ecfc8571ff0c26dfbe221d07",
|
"rev": "76f0a61e733259e1034dd6523e039d04932ffefc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -308,36 +120,6 @@
|
||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
10
flake.nix
10
flake.nix
|
|
@ -4,8 +4,6 @@
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
cargo-workspace.url = "github:Maix0/cargo-ws-flake";
|
|
||||||
cargo-semver-checks.url = "github:Maix0/cargo-semver-checks-flake";
|
|
||||||
naersk.url = "github:nix-community/naersk";
|
naersk.url = "github:nix-community/naersk";
|
||||||
};
|
};
|
||||||
outputs = {
|
outputs = {
|
||||||
|
|
@ -89,14 +87,6 @@
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export RUST_STD="${rust_dev}/share/doc/rust/html/std/index.html"
|
export RUST_STD="${rust_dev}/share/doc/rust/html/std/index.html"
|
||||||
|
|
||||||
if [ -z $CARGO_TARGET_DIR ] then
|
|
||||||
: "''${XDG_CACHE_HOME:="''$HOME/.cache"}"
|
|
||||||
local hash path
|
|
||||||
hash="$(sha1sum - <<< "$PWD" | head -c40)"
|
|
||||||
path="''${PWD//[^a-zA-Z0-9]/}"
|
|
||||||
export CARGO_TARGET_DIR="''${XDG_CACHE_HOME}/rust-targets/$hash-$path"
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
444
src/main.rs
444
src/main.rs
|
|
@ -20,144 +20,35 @@ use axum::{
|
||||||
};
|
};
|
||||||
use axum_extra::extract::{
|
use axum_extra::extract::{
|
||||||
cookie::{Cookie, Expiration, Key, SameSite},
|
cookie::{Cookie, Expiration, Key, SameSite},
|
||||||
CookieJar,
|
CookieJar, PrivateCookieJar,
|
||||||
};
|
};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
use color_eyre::eyre::Context;
|
||||||
|
use reqwest::tls::Version;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::{io::AsyncReadExt, sync::Mutex};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use oauth2::{
|
|
||||||
basic::*, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, EndpointNotSet,
|
|
||||||
EndpointSet, IntrospectionUrl, RedirectUrl, RefreshToken, TokenResponse, TokenUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
macro_rules! unwrap_env {
|
macro_rules! unwrap_env {
|
||||||
($name:literal) => {
|
($name:literal) => {
|
||||||
std::env::var($name).expect(&format!("missing `{}` env var", $name))
|
std::env::var($name).expect(&format!("missing `{}` env var", $name))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type OClient = BasicClient<EndpointSet, EndpointNotSet, EndpointSet, EndpointNotSet, EndpointSet>;
|
mod oauth2;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Token {
|
|
||||||
token: String,
|
|
||||||
refresh: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
async fn refresh(tok: &BearerToken, config: &AppState) -> Result<(), ()> {
|
|
||||||
let token = match tok {
|
|
||||||
BearerToken::User(i) => {
|
|
||||||
let users = config.users.write().unwrap();
|
|
||||||
users.get(&i).ok_or(())?.refresh.clone()
|
|
||||||
}
|
|
||||||
BearerToken::Provided(_) => return Err(()),
|
|
||||||
BearerToken::App => {
|
|
||||||
let code = config
|
|
||||||
.oauth
|
|
||||||
.exchange_client_credentials()
|
|
||||||
.request_async(&config.http)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
config.token.write().unwrap().token = code.access_token().secret().clone();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let refresh_token = RefreshToken::new(token);
|
|
||||||
|
|
||||||
match config
|
|
||||||
.oauth
|
|
||||||
.exchange_refresh_token(&refresh_token)
|
|
||||||
.request_async(&config.http)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Err(e) => Err(error!("Unable to refresh token ! {e}")),
|
|
||||||
Ok(t) => {
|
|
||||||
info!("Refreshed a token !");
|
|
||||||
match tok {
|
|
||||||
BearerToken::User(i) => {
|
|
||||||
let mut users = config.users.write().unwrap();
|
|
||||||
let u = users.get_mut(&i).ok_or(())?;
|
|
||||||
u.token = t.access_token().secret().clone();
|
|
||||||
u.refresh = t.refresh_token().unwrap().secret().clone();
|
|
||||||
}
|
|
||||||
_ => return Err(()),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
http: reqwest::Client,
|
http: reqwest::Client,
|
||||||
oauth: OClient,
|
oauth: Arc<oauth2::OauthClient>,
|
||||||
|
tutors: Arc<Mutex<HashSet<u64>>>,
|
||||||
key: Key,
|
key: Key,
|
||||||
users: Arc<RwLock<HashMap<u64, Token>>>,
|
|
||||||
token: Arc<RwLock<Token>>,
|
|
||||||
tutors: Arc<RwLock<HashSet<u64>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<AppState> for Key {
|
impl FromRef<AppState> for Key {
|
||||||
fn from_ref(state: &AppState) -> Self {
|
fn from_ref(input: &AppState) -> Self {
|
||||||
state.key.clone()
|
input.key.clone()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BearerToken {
|
|
||||||
App,
|
|
||||||
User(u64),
|
|
||||||
Provided(Token),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppState {
|
|
||||||
async fn do_request<R: DeserializeOwned>(
|
|
||||||
&self,
|
|
||||||
url: impl reqwest::IntoUrl + Clone,
|
|
||||||
query: impl Serialize,
|
|
||||||
mut tok: BearerToken,
|
|
||||||
) -> Result<R, ()> {
|
|
||||||
let res = {
|
|
||||||
let mut users = self.users.write().unwrap();
|
|
||||||
let mut lock = match &tok {
|
|
||||||
BearerToken::App => Some(self.token.write().unwrap()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let token = match &mut lock {
|
|
||||||
Some(s) => Ok(&mut **s),
|
|
||||||
None => {
|
|
||||||
if let BearerToken::User(id) = &tok {
|
|
||||||
users.get_mut(&id).ok_or(())
|
|
||||||
} else if let BearerToken::Provided(t) = &mut tok {
|
|
||||||
Ok(t)
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
self.http
|
|
||||||
.get(url.clone())
|
|
||||||
.bearer_auth(&token.token)
|
|
||||||
.query(&query)
|
|
||||||
.send()
|
|
||||||
};
|
|
||||||
let res = res.await.map_err(|e| error!("Error with request {e}"))?;
|
|
||||||
let json = res
|
|
||||||
.json::<Value>()
|
|
||||||
.await
|
|
||||||
.map_err(|e| error!("Error with request response {e}"))?;
|
|
||||||
|
|
||||||
if !json["error"].is_null() && json["message"].as_str() == Some("The access token expired")
|
|
||||||
{
|
|
||||||
Token::refresh(&tok, self).await?;
|
|
||||||
return Box::pin(Self::do_request(self, url, query, tok)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
serde_json::from_value(json).map_err(|e| error!("error when parsing json {e}"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,19 +65,19 @@ struct GroupsUsers {
|
||||||
async fn tutors(config: AppState) {
|
async fn tutors(config: AppState) {
|
||||||
loop {
|
loop {
|
||||||
{
|
{
|
||||||
let mut lock = config.tutors.write().unwrap();
|
let mut lock = config.tutors.lock().await;
|
||||||
lock.clear();
|
lock.clear();
|
||||||
let mut page_nb = 0;
|
let mut page_nb = 0;
|
||||||
loop {
|
loop {
|
||||||
info!("tutor request (page {page_nb})");
|
info!("tutor request (page {page_nb})");
|
||||||
let res = config
|
let res = config
|
||||||
.do_request::<Vec<GroupsUsers>>(
|
.oauth
|
||||||
|
.do_request::<Vec<User42>>(
|
||||||
"https://api.intra.42.fr/v2/groups/166/users",
|
"https://api.intra.42.fr/v2/groups/166/users",
|
||||||
json! ({
|
&json! ({
|
||||||
"page[number]": page_nb,
|
"page[number]": page_nb,
|
||||||
"page[size]": 100,
|
"page[size]": 100,
|
||||||
}),
|
}),
|
||||||
BearerToken::App,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -209,27 +100,13 @@ async fn main() {
|
||||||
local
|
local
|
||||||
.run_until(async {
|
.run_until(async {
|
||||||
// initialize tracing
|
// initialize tracing
|
||||||
let oauth = BasicClient::new(ClientId::new(unwrap_env!("CLIENT_ID")))
|
|
||||||
.set_redirect_uri(
|
|
||||||
RedirectUrl::new("https://t.maix.me/auth/callback".to_string()).unwrap(),
|
|
||||||
)
|
|
||||||
.set_introspection_url(
|
|
||||||
IntrospectionUrl::new("https://api.intra.42.fr/oauth/token/info".to_string())
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.set_client_secret(ClientSecret::new(unwrap_env!("CLIENT_SECRET")))
|
|
||||||
.set_auth_uri(
|
|
||||||
AuthUrl::new("https://api.intra.42.fr/oauth/authorize".to_string())
|
|
||||||
.expect("invalid authUrl"),
|
|
||||||
)
|
|
||||||
.set_token_uri(
|
|
||||||
TokenUrl::new("https://api.intra.42.fr/oauth/token".to_string())
|
|
||||||
.expect("invalid tokenUrl"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let http = reqwest::ClientBuilder::new()
|
let http = reqwest::ClientBuilder::new()
|
||||||
// Following redirects opens the client up to SSRF vulnerabilities.
|
// Following redirects opens the client up to SSRF vulnerabilities.
|
||||||
.redirect(reqwest::redirect::Policy::none())
|
.redirect(reqwest::redirect::Policy::none())
|
||||||
|
.user_agent("AlterPoste/1.0")
|
||||||
|
.tls_info(true)
|
||||||
|
.min_tls_version(Version::TLS_1_0)
|
||||||
|
.max_tls_version(Version::TLS_1_2)
|
||||||
.build()
|
.build()
|
||||||
.expect("Client should build");
|
.expect("Client should build");
|
||||||
|
|
||||||
|
|
@ -238,22 +115,20 @@ async fn main() {
|
||||||
.decode(cookie_secret)
|
.decode(cookie_secret)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let key: Key = Key::from(&base64_value);
|
let key: Key = Key::from(&base64_value);
|
||||||
|
let oauth = oauth2::OauthClient::new(
|
||||||
let code = oauth
|
http.clone(),
|
||||||
.exchange_client_credentials()
|
unwrap_env!("CLIENT_ID"),
|
||||||
.request_async(&http)
|
unwrap_env!("CLIENT_SECRET"),
|
||||||
|
"http://local.maix.me/auth/callback",
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
token: Arc::new(RwLock::new(Token {
|
|
||||||
token: code.access_token().secret().clone(),
|
|
||||||
refresh: String::new(),
|
|
||||||
})),
|
|
||||||
tutors: Default::default(),
|
|
||||||
key,
|
|
||||||
http,
|
http,
|
||||||
oauth,
|
key,
|
||||||
users: Default::default(),
|
oauth: Arc::new(oauth),
|
||||||
|
tutors: Default::default(),
|
||||||
};
|
};
|
||||||
tokio::task::spawn_local(tutors(state.clone()));
|
tokio::task::spawn_local(tutors(state.clone()));
|
||||||
|
|
||||||
|
|
@ -265,15 +140,19 @@ async fn main() {
|
||||||
.route("/stop", get(stop))
|
.route("/stop", get(stop))
|
||||||
.route("/start", get(start))
|
.route("/start", get(start))
|
||||||
.route("/restart", get(restart))
|
.route("/restart", get(restart))
|
||||||
.route("/config", get(get_config))
|
|
||||||
.route("/db", get(get_db))
|
|
||||||
.route("/pull", get(git_pull))
|
.route("/pull", get(git_pull))
|
||||||
.route("/auth/callback", get(oauth2_callback))
|
.route("/auth/callback", get(oauth2_callback))
|
||||||
.route("/auth/login", get(oauth2_login))
|
.route("/auth/login", get(oauth2_login))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
// run our app with hyper
|
// run our app with hyper
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:9911")
|
let listener = tokio::net::TcpListener::bind(format!(
|
||||||
|
"127.0.0.1:{}",
|
||||||
|
std::env::args()
|
||||||
|
.nth(1)
|
||||||
|
.and_then(|s| s.parse::<u16>().ok())
|
||||||
|
.unwrap_or(9911)
|
||||||
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||||
|
|
@ -282,9 +161,18 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn oauth2_login(State(state): State<AppState>) -> Redirect {
|
async fn oauth2_login(State(state): State<AppState>) -> Result<Redirect, StatusCode> {
|
||||||
let (url, _) = state.oauth.authorize_url(CsrfToken::new_random).url();
|
Ok(Redirect::to(
|
||||||
Redirect::to(url.as_str())
|
&(state
|
||||||
|
.oauth
|
||||||
|
.get_auth_url()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?
|
||||||
|
.to_string()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
use time::Duration as TDuration;
|
use time::Duration as TDuration;
|
||||||
|
|
@ -294,95 +182,46 @@ use time::OffsetDateTime;
|
||||||
async fn oauth2_callback(
|
async fn oauth2_callback(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<HashMap<String, String>>,
|
Query(params): Query<HashMap<String, String>>,
|
||||||
jar: CookieJar,
|
jar: PrivateCookieJar,
|
||||||
) -> impl IntoResponse {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
let inner = || async {
|
||||||
let Some(code) = params.get("code") else {
|
let Some(code) = params.get("code") else {
|
||||||
return (jar, Redirect::to("/"));
|
return Ok::<_, color_eyre::eyre::Report>((jar, Redirect::to("/")));
|
||||||
};
|
};
|
||||||
let Some(state_csrf) = params.get("state") else {
|
let Some(state_csrf) = params.get("state") else {
|
||||||
return (jar, Redirect::to("/"));
|
return Ok((jar, Redirect::to("/")));
|
||||||
};
|
};
|
||||||
let mut form_data = HashMap::new();
|
let token = state
|
||||||
form_data.insert("grant_type", "authorization_code".to_string());
|
.oauth
|
||||||
form_data.insert("client_id", unwrap_env!("CLIENT_ID"));
|
.get_user_token(code, state_csrf)
|
||||||
form_data.insert("client_secret", unwrap_env!("CLIENT_SECRET"));
|
.await
|
||||||
form_data.insert("code", code.to_string());
|
.wrap_err("callback")?;
|
||||||
form_data.insert(
|
|
||||||
"redirect_uri",
|
let rep = state
|
||||||
state.oauth.redirect_uri().unwrap().to_string(),
|
|
||||||
);
|
|
||||||
form_data.insert("state", state_csrf.to_string());
|
|
||||||
let token_res = match state
|
|
||||||
.http
|
.http
|
||||||
.post(state.oauth.token_uri().as_str())
|
.get("https://api.intra.42.fr/v2/users/me")
|
||||||
.form(&form_data)
|
.bearer_auth(&token.access_token)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
{
|
.wrap_err("Unable to get user self")?;
|
||||||
Ok(o) => o.json::<Value>().await.unwrap(),
|
|
||||||
Err(e) => {
|
|
||||||
error!("{e}");
|
|
||||||
return (jar, Redirect::to("/auth/error"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let Ok(rep) = state
|
|
||||||
.do_request::<User42>(
|
|
||||||
"https://api.intra.42.fr/v2/me",
|
|
||||||
(),
|
|
||||||
BearerToken::Provided(
|
|
||||||
match token_res["access_token"].as_str().ok_or(()).and_then(|t| {
|
|
||||||
token_res["refresh_token"]
|
|
||||||
.as_str()
|
|
||||||
.ok_or(())
|
|
||||||
.map(|r| Token {
|
|
||||||
token: t.to_string(),
|
|
||||||
refresh: r.to_string(),
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return (jar, Redirect::to("/error/")),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
info!("failed to get id");
|
|
||||||
return (jar, Redirect::to("/error/"));
|
|
||||||
};
|
|
||||||
|
|
||||||
if !state.tutors.read().unwrap().contains(&rep.id) {
|
let json: User42 = rep.json().await.wrap_err("unable to parse api reply")?;
|
||||||
info!("non tutor tried to login");
|
let mut cookie = Cookie::new("token", json.id.to_string());
|
||||||
return (jar, Redirect::to("/error/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cookie = Cookie::new("token", rep.id.to_string());
|
|
||||||
let mut now = OffsetDateTime::now_utc();
|
|
||||||
now += TDuration::weeks(52);
|
|
||||||
cookie.set_expires(Some(now));
|
|
||||||
cookie.set_same_site(SameSite::None);
|
cookie.set_same_site(SameSite::None);
|
||||||
cookie.set_secure(true);
|
cookie.set_secure(true);
|
||||||
// cookie.set_domain("localhost:3000");
|
|
||||||
cookie.set_path("/");
|
cookie.set_path("/");
|
||||||
|
// cookie.set_domain("localhost:3000");
|
||||||
// cookie.set_http_only(Some(false));
|
// cookie.set_http_only(Some(false));
|
||||||
state.users.write().unwrap().insert(
|
|
||||||
rep.id,
|
|
||||||
match token_res["access_token"].as_str().ok_or(()).and_then(|t| {
|
|
||||||
dbg!(&token_res)["refresh_token"]
|
|
||||||
.as_str()
|
|
||||||
.ok_or(())
|
|
||||||
.map(|r| Token {
|
|
||||||
token: t.to_string(),
|
|
||||||
refresh: r.to_string(),
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return (jar, Redirect::to("/error/")),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let ujar = jar.add(cookie);
|
let ujar = jar.add(cookie);
|
||||||
info!("logged in");
|
Ok((ujar, Redirect::to("/")))
|
||||||
(ujar, Redirect::to("/"))
|
};
|
||||||
|
match inner().await {
|
||||||
|
Ok(ret) => Ok(ret),
|
||||||
|
Err(e) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -420,7 +259,7 @@ impl FromRequestParts<AppState> for UserLoggedIn {
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
if state.tutors.read().unwrap().contains(&user_id) {
|
if state.tutors.lock().await.contains(&user_id) {
|
||||||
info!("is tut");
|
info!("is tut");
|
||||||
Ok(UserLoggedIn { id: user_id })
|
Ok(UserLoggedIn { id: user_id })
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -444,7 +283,6 @@ async fn root(_user: UserLoggedIn) -> Html<&'static str> {
|
||||||
<a href="/stop">stop</a><br>
|
<a href="/stop">stop</a><br>
|
||||||
<a href="/start">start</a><br>
|
<a href="/start">start</a><br>
|
||||||
<a href="/status">status</a><br>
|
<a href="/status">status</a><br>
|
||||||
<a href="/config">config</a><br>
|
|
||||||
<a href="/db">db</a><br>
|
<a href="/db">db</a><br>
|
||||||
<a href="/pull">git pull (ask before!)</a><br>
|
<a href="/pull">git pull (ask before!)</a><br>
|
||||||
"#,
|
"#,
|
||||||
|
|
@ -484,137 +322,10 @@ async fn stop(_user: UserLoggedIn) -> Redirect {
|
||||||
Redirect::to("/")
|
Redirect::to("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct BotConfig {
|
|
||||||
piscine: Vec<String>,
|
|
||||||
pc_tut: Vec<String>,
|
|
||||||
id_server: u64,
|
|
||||||
id_channel_alerte: u64,
|
|
||||||
id_role: u64,
|
|
||||||
mois: String,
|
|
||||||
annee: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_config(_user: UserLoggedIn) -> Result<Json<BotConfig>, (StatusCode, String)> {
|
|
||||||
info!("Requested config");
|
|
||||||
let Ok(mut file) = tokio::fs::File::open(
|
|
||||||
std::env::var("BOTLOC_DIR").map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"please set env CONFIG_PATH".to_string(),
|
|
||||||
)
|
|
||||||
})? + "/config.json",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
e
|
|
||||||
}) else {
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!(
|
|
||||||
"failed to open config file: {}/config.json",
|
|
||||||
std::env::var("BOTLOC_DIR").unwrap()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
if file
|
|
||||||
.read_to_string(&mut s)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
e
|
|
||||||
})
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!(
|
|
||||||
"Failed to read config file at {}/config.json",
|
|
||||||
std::env::var("BOTLOC_DIR").unwrap()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let Ok(val) = serde_json::from_str::<BotConfig>(&s).map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
e
|
|
||||||
}) else {
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!(
|
|
||||||
"Failed to read config file as json at {}/config.json",
|
|
||||||
std::env::var("BOTLOC_DIR").unwrap()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
Ok(Json(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_db(_user: UserLoggedIn) -> Result<Json<Value>, (StatusCode, String)> {
|
|
||||||
info!("Requested config");
|
|
||||||
let Ok(mut file) = tokio::fs::File::open(
|
|
||||||
std::env::var("BOTLOC_DIR").map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"please set env CONFIG_PATH".to_string(),
|
|
||||||
)
|
|
||||||
})? + "/db.json",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
e
|
|
||||||
}) else {
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!(
|
|
||||||
"failed to open config file: {}/db.json",
|
|
||||||
std::env::var("BOTLOC_DIR").unwrap()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
if file
|
|
||||||
.read_to_string(&mut s)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
e
|
|
||||||
})
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!(
|
|
||||||
"Failed to read config file at {}/db.json",
|
|
||||||
std::env::var("BOTLOC_DIR").unwrap()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let Ok(val) = serde_json::from_str::<Value>(&s).map_err(|e| {
|
|
||||||
error!("Failed to open config file: {e}");
|
|
||||||
e
|
|
||||||
}) else {
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!(
|
|
||||||
"Failed to read config file as json at {}/db.json",
|
|
||||||
std::env::var("BOTLOC_DIR").unwrap()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
Ok(Json(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn status() -> Result<String, StatusCode> {
|
async fn status() -> Result<String, StatusCode> {
|
||||||
info!("Requested status");
|
info!("Requested status");
|
||||||
let mut output = tokio::process::Command::new("systemctl")
|
let mut output = tokio::process::Command::new("journalctl")
|
||||||
.args(["--user", "status", "botloc.service"])
|
.args(["-xeu", "botloc"])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
// let mut output = child.wait_with_output().await
|
// let mut output = child.wait_with_output().await
|
||||||
|
|
@ -631,7 +342,6 @@ async fn status() -> Result<String, StatusCode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn git_pull() -> Result<String, (StatusCode, &'static str)> {
|
async fn git_pull() -> Result<String, (StatusCode, &'static str)> {
|
||||||
dbg!(std::env::var("PATH"));
|
|
||||||
info!("Requested to pull");
|
info!("Requested to pull");
|
||||||
let mut output = tokio::process::Command::new("/home/maix/.nix-profile/bin/git")
|
let mut output = tokio::process::Command::new("/home/maix/.nix-profile/bin/git")
|
||||||
.current_dir(std::env::var("BOTLOC_DIR").map_err(|e| {
|
.current_dir(std::env::var("BOTLOC_DIR").map_err(|e| {
|
||||||
|
|
|
||||||
134
src/oauth2.rs
Normal file
134
src/oauth2.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
|
use color_eyre::eyre::{self, WrapErr};
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct OauthClient {
|
||||||
|
client_id: String,
|
||||||
|
client_secret: String,
|
||||||
|
redirect_uri: String,
|
||||||
|
token: Token,
|
||||||
|
http: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Token {
|
||||||
|
#[serde(default)]
|
||||||
|
refresh_token: Option<String>,
|
||||||
|
pub access_token: String,
|
||||||
|
token_type: String,
|
||||||
|
expires_in: u64,
|
||||||
|
scope: String,
|
||||||
|
created_at: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OauthClient {
|
||||||
|
async fn get_app_token(
|
||||||
|
client: reqwest::Client,
|
||||||
|
uid: impl AsRef<str>,
|
||||||
|
secret: impl AsRef<str>,
|
||||||
|
) -> eyre::Result<Token> {
|
||||||
|
let uid = uid.as_ref();
|
||||||
|
let secret = secret.as_ref();
|
||||||
|
|
||||||
|
let mut form_data = HashMap::new();
|
||||||
|
form_data.insert("grant_type", "client_credentials");
|
||||||
|
form_data.insert("client_id", uid);
|
||||||
|
form_data.insert("client_secret", secret);
|
||||||
|
let response = client
|
||||||
|
.post("https://api.intra.42.fr/oauth/token")
|
||||||
|
.form(&form_data)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.wrap_err("Sending request to fetch 42 API token")?;
|
||||||
|
let body = response.bytes().await?;
|
||||||
|
let text = String::from_utf8_lossy(&body);
|
||||||
|
println!("{}", text);
|
||||||
|
let json: Token = serde_json::from_slice(&body).unwrap(); // response.json().await.wrap_err("API response to json")?;
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
pub async fn new(
|
||||||
|
client: reqwest::Client,
|
||||||
|
uid: impl AsRef<str>,
|
||||||
|
secret: impl AsRef<str>,
|
||||||
|
redirect_uri: impl AsRef<str>,
|
||||||
|
) -> eyre::Result<Self> {
|
||||||
|
let uid = uid.as_ref();
|
||||||
|
let secret = secret.as_ref();
|
||||||
|
let redirect_uri = redirect_uri.as_ref().to_string();
|
||||||
|
let token = Self::get_app_token(client.clone(), uid, secret).await?;
|
||||||
|
Ok(Self {
|
||||||
|
client_id: uid.to_string(),
|
||||||
|
client_secret: secret.to_string(),
|
||||||
|
token,
|
||||||
|
redirect_uri,
|
||||||
|
http: client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_auth_url(&self) -> eyre::Result<http::Uri> {
|
||||||
|
let redirect_uri =
|
||||||
|
pct_str::PctString::encode(self.redirect_uri.chars(), pct_str::URIReserved);
|
||||||
|
let csrf = [(); 64].map(|()| rand::random());
|
||||||
|
|
||||||
|
let uri = http::Uri::builder()
|
||||||
|
.scheme("https")
|
||||||
|
.authority("api.intra.42.fr")
|
||||||
|
.path_and_query(format!(
|
||||||
|
"/oauth/authorize?client_id={}&scope=public&response_type=code&redirect_uri={redirect_uri}&code={}",
|
||||||
|
self.client_id, base64::engine::general_purpose::URL_SAFE.encode(csrf)
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
.wrap_err("Failed to build URI")?;
|
||||||
|
Ok(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_token(
|
||||||
|
&self,
|
||||||
|
code: impl AsRef<str>,
|
||||||
|
csrf: impl AsRef<str>,
|
||||||
|
) -> eyre::Result<Token> {
|
||||||
|
let code = code.as_ref();
|
||||||
|
let csrf = csrf.as_ref();
|
||||||
|
|
||||||
|
let mut form_data = HashMap::new();
|
||||||
|
form_data.insert("code", code);
|
||||||
|
form_data.insert("state", csrf);
|
||||||
|
form_data.insert("client_id", &self.client_id);
|
||||||
|
form_data.insert("client_secret", &self.client_secret);
|
||||||
|
form_data.insert("redirect_uri", &self.redirect_uri);
|
||||||
|
form_data.insert("grant_type", "authorization_code");
|
||||||
|
let response = self
|
||||||
|
.http
|
||||||
|
.post("https://api.intra.42.fr/oauth/token")
|
||||||
|
.form(&form_data)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.wrap_err("Failed to get token for user")?;
|
||||||
|
let json: Token = response.json().await.wrap_err("API response to json")?;
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn do_request<R: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
url: impl AsRef<str>,
|
||||||
|
qs: &impl Serialize,
|
||||||
|
) -> eyre::Result<R> {
|
||||||
|
let url = url.as_ref();
|
||||||
|
let req = self
|
||||||
|
.http
|
||||||
|
.get(url)
|
||||||
|
.query(qs)
|
||||||
|
.bearer_auth(&self.token.access_token)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.wrap_err("Failed to send request")?;
|
||||||
|
let json = req
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.wrap_err("Failed to Deserialize response")?;
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
thread::spawn,
|
|
||||||
time::SystemTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AxumState {
|
|
||||||
db: Arc<Mutex<HashMap<String, ItemOauthAxum>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ItemOauthAxum {
|
|
||||||
pub verifier: String,
|
|
||||||
pub created_at: SystemTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AxumState {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let db: Arc<Mutex<HashMap<String, ItemOauthAxum>>> = Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
let db_binding = Arc::clone(&db);
|
|
||||||
spawn(move || loop {
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
|
||||||
let mut db = db_binding.lock().unwrap();
|
|
||||||
let now = SystemTime::now();
|
|
||||||
db.retain(|_, item| now.duration_since(item.created_at).unwrap().as_secs() < 900);
|
|
||||||
});
|
|
||||||
AxumState {
|
|
||||||
db: Arc::clone(&db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, key: String) -> Option<String> {
|
|
||||||
let db = self.db.lock().unwrap();
|
|
||||||
if let Some(item) = db.get(&key) {
|
|
||||||
Some(item.verifier.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&self, key: String, value: String) {
|
|
||||||
let mut db = self.db.lock().unwrap();
|
|
||||||
db.insert(
|
|
||||||
key,
|
|
||||||
ItemOauthAxum {
|
|
||||||
verifier: value,
|
|
||||||
created_at: SystemTime::now(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_all_items(&self) -> Vec<ItemOauthAxum> {
|
|
||||||
let db = self.db.lock().unwrap();
|
|
||||||
db.values().cloned().collect::<Vec<ItemOauthAxum>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
pub mod memory_db_utils;
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue