Compare commits

..

2 Commits

Author SHA1 Message Date
SadlyNotSappho d6c418a899 its half broken but roles are somewhat implemented 2024-02-06 12:03:00 -08:00
SadlyNotSappho 59d0ed12c1 create admin user, partially rebrand to login template 2024-02-06 11:35:25 -08:00
6 changed files with 140 additions and 86 deletions

8
.env.example Normal file
View File

@ -0,0 +1,8 @@
POSTGRES_DB=fossil_postgres
POSTGRES_PASSWORD=password
ROCKET_SECRET_KEY=openssl rand -base64 32
ADMIN_USERNAME=admin
ADMIN_PASSWORD=password

3
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target
docker-compose.yml
db/password.txt
.env

View File

@ -1,9 +1,7 @@
# fossil
# login template
Free and Open Source Software - Library
this is pretty much just some shitty code that'll handle logging into and out of a website, with the default features being a blog, but it should be easy to change to something else
yeah yeah there's no i in the name, i just wanted the acronym to be an actual word
setup: remove .example from `docker-compose.yml.example` and `db/password.txt.example`
\nfill out those files with the correct values
setup: remove .example from `.env.example`
\nfill it with the correct values (for `ROCKET_SECRET_KEY`, run that command to generate it)
\nrun `docker compose build` to build it, `docker compose run` to run, and `docker compose run -d` to run it in the background

View File

@ -15,14 +15,16 @@ services:
ports:
- 8000:8000
environment:
# name in main.rs db.user POSTGRES_PASSWORD POSTGRES_DB
- ROCKET_DATABASES={diesel_postgres={url="postgres://postgres:passwordpasswordpassword@db/example"}}
- ROCKET_DATABASES={fossil_postgres={url="postgres://postgres:$POSTGRES_PASSWORD@db/$POSTGRES_DB", max_connections=10, connect_timeout=10}}
- ROCKET_ADDRESS=0.0.0.0
- ROCKET_PORT=8000
- ROCKET_PORT=$PORT
- RUST_LOG=debug
- ROCKET_SECRET_KEY=openssl rand -base64 32
- ROCKET_SECRET_KEY=$ROCKET_SECRET_KEY
- ADMIN_USERNAME=$ADMIN_USERNAME
- ADMIN_PASSWORD=$ADMIN_PASSWORD
volumes:
- type: bind
source: ./web
@ -40,13 +42,11 @@ services:
image: postgres
restart: always
user: postgres
secrets:
- db-password
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=example
- POSTGRES_PASSWORD=passwordpasswordpassword
- POSTGRES_DB=$POSTGRES_DB
- POSTGRES_PASSWORD=$POSTGRES_PASSWORD
expose:
- 5432
healthcheck:
@ -56,6 +56,3 @@ services:
retries: 5
volumes:
db-data:
secrets:
db-password:
file: db/password.txt

View File

@ -69,34 +69,18 @@ async fn createuser(
}
}
#[get("/getuser/<username>")]
async fn getuser(db: Connection<Db>, username: String) -> String {
let user = User::get_by_username(db, &username).await;
match user {
Some(user) => format!(
"id: {}\nusername: {}\nhashed password: {}\ntoken: {}",
user.id,
user.username,
user.password,
match user.token {
Some(t) => t,
None => "no token".to_string()
}
),
None => format!("User {} doesn't exist.", &username),
}
}
#[get("/account")]
async fn account(db: Connection<Db>, cookies: &CookieJar<'_>) -> String {
let token = cookies.get_private("token");
match token {
Some(t) => {
let user = User::get_by_token(db, t.to_string().split("=").collect::<Vec<&str>>()[1].to_string() /*GOD I LOVE RUST*/).await;
format!("Username: {}", user.username)
match User::get_by_token(db, t.to_string().split('=').collect::<Vec<&str>>()[1].to_string() /*GOD I LOVE RUST*/).await {
Some(user) => format!("Username: {}\nAdmin: {}\nMake Posts: {}\nComment: {}", user.username, user.admin, user.make_posts, user.comment),
None => "User doesn't exist.".to_string()
}
},
None => {
format!("Not logged in")
"Not logged in".to_string()
}
}
}
@ -105,12 +89,8 @@ async fn account(db: Connection<Db>, cookies: &CookieJar<'_>) -> String {
async fn login(db: Connection<Db>, db2: Connection<Db>, info: Json<LoginInfo>, cookies: &CookieJar<'_>) -> String {
let token = cookies.get_private("token");
match token {
Some(t) => {
if User::get_by_token(db, t.to_string()).await.exists() /*god i fucking love rust, this function literally just returns true*/ {
"logged in with token".to_string()
} else {
"unknown token".to_string()
}
Some(_) => {
"already logged in".to_string()
}
None => {
match User::get_by_username(db, &info.username).await {
@ -140,12 +120,12 @@ async fn login(db: Connection<Db>, db2: Connection<Db>, info: Json<LoginInfo>, c
}
#[post("/logout")]
async fn logout(cookies: &CookieJar<'_>) -> &'static str {
let token = cookies.get_private("token");
if token.is_some() {
cookies.remove_private("token");
"logged out"
} else {
"not logged in"
match cookies.get_private("token") {
Some(_) => {
cookies.remove_private("token");
"Logged out."
},
None => "Not logged in."
}
}
@ -192,7 +172,7 @@ async fn migrate(rocket: Rocket<Build>) -> Rocket<Build> {
)",
))
.await;
let _2 = conn
let _ = conn
.fetch_one(sqlx::query(
"CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
@ -203,6 +183,38 @@ async fn migrate(rocket: Rocket<Build>) -> Rocket<Build> {
))
.await;
let _ = conn.fetch_one(sqlx::query(
"ALTER TABLE users
ADD COLUMN IF NOT EXISTS admin BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS make_posts BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS comment BOOLEAN NOT NULL DEFAULT TRUE
"
)).await;
match conn.fetch_one(sqlx::query("SELECT * from users WHERE username = $1").bind(std::env::var("ADMIN_USERNAME").expect("make ADMIN_USERNAME env var"))).await {
Ok(_) => (),
Err(_) => {
// yes, User::create() exists. no, conn isn't the right type. no, i won't even attempt
// to fix it. yes, this works just as well as that. fuck you.
let username = std::env::var("ADMIN_USERNAME").expect("make ADMIN_USERNAME env var");
let password = sha256::digest(std::env::var("ADMIN_PASSWORD").expect("make ADMIN_PASSWORD env var"));
eprintln!("{username}\n{password}");
conn.fetch_one(sqlx::query(
r#"
INSERT INTO users (username, password, admin)
VALUES ($1, $2, true);
"#,
)
.bind(username)
.bind(password)
)
.await
.expect("couldn't create admin user");
}
}
rocket
}
@ -214,7 +226,7 @@ async fn main() {
.attach(AdHoc::on_ignite("DB Migrations", migrate))
.mount(
"/",
routes![hello, get_book, delay, login, logout, dbtest, dbcreate, createuser, getuser, account],
routes![hello, get_book, delay, login, logout, dbtest, dbcreate, createuser, account],
)
.register("/", catchers![default_catcher])
.mount("/login", FileServer::from("/srv/web"))

View File

@ -1,4 +1,4 @@
use rand::{SeedableRng, RngCore};
use rand::{RngCore, SeedableRng};
use rocket_db_pools::sqlx::Executor;
use rocket_db_pools::Connection;
use sqlx::FromRow;
@ -60,6 +60,14 @@ pub struct User {
pub username: String,
pub password: String,
pub token: Option<String>,
pub admin: bool,
pub make_posts: bool,
pub comment: bool,
}
pub enum UserRole {
Admin,
MakePosts,
Comment,
}
impl User {
pub async fn create(mut db: Connection<Db>, username: &String, password: &String) {
@ -82,42 +90,53 @@ impl User {
}
}
}
pub async fn get_by_id(mut db: Connection<Db>, id: i32) -> User {
let res = db
pub async fn get_by_id(mut db: Connection<Db>, id: i32) -> Option<User> {
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id))
.await
.unwrap();
User {
id: res.get::<i32, _>("id"),
username: res.get::<String, _>("username"),
password: res.get::<String, _>("password"),
token: res.get::<Option<String>, _>("token"),
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn get_by_token(mut db: Connection<Db>, token: String) -> User {
let res = db
pub async fn get_by_token(mut db: Connection<Db>, token: String) -> Option<User> {
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE token = $1;").bind(token))
.await
// TODO: this errors sometimes i dont know why
.unwrap();
User {
id: res.get::<i32, _>("id"),
username: res.get::<String, _>("username"),
password: res.get::<String, _>("password"),
token: res.get::<Option<String>, _>("token"),
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn get_by_username(mut db: Connection<Db>, username: &String) -> Option<User> {
let res = db
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username))
.await;
match res {
Ok(res) => Some(User {
id: res.get::<i32, _>("id"),
username: res.get::<String, _>("username"),
password: res.get::<String, _>("password"),
token: res.get::<Option<String>, _>("token"),
.await
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
@ -128,14 +147,35 @@ impl User {
let token = sha256::digest(format!("{token_start}-{token_end}"));
match db.fetch_one(sqlx::query("UPDATE users SET token = $1 WHERE id = $2").bind(&token).bind(self.id)).await {
match db
.fetch_one(
sqlx::query("UPDATE users SET token = $1 WHERE id = $2")
.bind(&token)
.bind(self.id),
)
.await
{
Ok(_) => Ok(token),
Err(why) => Err(why.to_string())
Err(why) => Err(why.to_string()),
}
}
// god i fucking love rust
pub fn exists(&self) -> bool {
true
pub async fn set_role(&self, mut db: Connection<Db>, role: UserRole, value: bool) -> Result<String, String> {
match db
.fetch_one(
sqlx::query("UPDATE users SET $1 = $2 WHERE id = $3")
.bind(match role {
UserRole::Admin => "admin",
UserRole::MakePosts => "make_posts",
UserRole::Comment => "comment",
})
.bind(value)
.bind(self.id),
)
.await
{
Ok(_) => Ok("Succesfully updated role.".to_string()),
Err(why) => Err(why.to_string()),
}
}
}