allow creation of user and links via args and env
This commit is contained in:
parent
41d2ec5793
commit
7dd92878ed
3 changed files with 111 additions and 8 deletions
15
src/auth.rs
15
src/auth.rs
|
@ -1,7 +1,7 @@
|
||||||
|
use crate::{error::AppError, models::Claims};
|
||||||
use actix_web::{dev::Payload, FromRequest, HttpRequest};
|
use actix_web::{dev::Payload, FromRequest, HttpRequest};
|
||||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
use std::future::{ready, Ready};
|
use std::future::{ready, Ready};
|
||||||
use crate::{error::AppError, models::Claims};
|
|
||||||
|
|
||||||
pub struct AuthenticatedUser {
|
pub struct AuthenticatedUser {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
|
@ -12,19 +12,20 @@ impl FromRequest for AuthenticatedUser {
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
let auth_header = req.headers()
|
let auth_header = req
|
||||||
|
.headers()
|
||||||
.get("Authorization")
|
.get("Authorization")
|
||||||
.and_then(|h| h.to_str().ok());
|
.and_then(|h| h.to_str().ok());
|
||||||
|
|
||||||
if let Some(auth_header) = auth_header {
|
if let Some(auth_header) = auth_header {
|
||||||
if auth_header.starts_with("Bearer ") {
|
if auth_header.starts_with("Bearer ") {
|
||||||
let token = &auth_header[7..];
|
let token = &auth_header[7..];
|
||||||
let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "default_secret".to_string());
|
let secret =
|
||||||
|
std::env::var("JWT_SECRET").unwrap_or_else(|_| "default_secret".to_string());
|
||||||
match decode::<Claims>(
|
match decode::<Claims>(
|
||||||
token,
|
token,
|
||||||
&DecodingKey::from_secret(secret.as_bytes()),
|
&DecodingKey::from_secret(secret.as_bytes()),
|
||||||
&Validation::default()
|
&Validation::default(),
|
||||||
) {
|
) {
|
||||||
Ok(token_data) => {
|
Ok(token_data) => {
|
||||||
return ready(Ok(AuthenticatedUser {
|
return ready(Ok(AuthenticatedUser {
|
||||||
|
@ -35,7 +36,7 @@ impl FromRequest for AuthenticatedUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(Err(AppError::Unauthorized))
|
ready(Err(AppError::Unauthorized))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ fn validate_custom_code(code: &str) -> Result<(), AppError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_url(url: &String) -> Result<(), AppError> {
|
fn validate_url(url: &str) -> Result<(), AppError> {
|
||||||
if url.is_empty() {
|
if url.is_empty() {
|
||||||
return Err(AppError::InvalidInput("URL cannot be empty".to_string()));
|
return Err(AppError::InvalidInput("URL cannot be empty".to_string()));
|
||||||
}
|
}
|
||||||
|
|
102
src/main.rs
102
src/main.rs
|
@ -1,8 +1,10 @@
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::{web, App, HttpResponse, HttpServer};
|
use actix_web::{web, App, HttpResponse, HttpServer};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use simplelink::check_and_generate_admin_token;
|
use simplelink::check_and_generate_admin_token;
|
||||||
|
use simplelink::models::DatabasePool;
|
||||||
use simplelink::{create_db_pool, run_migrations};
|
use simplelink::{create_db_pool, run_migrations};
|
||||||
use simplelink::{handlers, AppState};
|
use simplelink::{handlers, AppState};
|
||||||
use sqlx::{Postgres, Sqlite};
|
use sqlx::{Postgres, Sqlite};
|
||||||
|
@ -26,6 +28,106 @@ async fn serve_static_file(path: &str) -> HttpResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_initial_links(pool: &DatabasePool) -> Result<()> {
|
||||||
|
if let Ok(links) = std::env::var("INITIAL_LINKS") {
|
||||||
|
for link_entry in links.split(';') {
|
||||||
|
let parts: Vec<&str> = link_entry.split(',').collect();
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
let url = parts[0];
|
||||||
|
let code = parts[1];
|
||||||
|
|
||||||
|
match pool {
|
||||||
|
DatabasePool::Postgres(pool) => {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO links (original_url, short_code, user_id)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (short_code)
|
||||||
|
DO UPDATE SET short_code = EXCLUDED.short_code
|
||||||
|
WHERE links.original_url = EXCLUDED.original_url",
|
||||||
|
)
|
||||||
|
.bind(url)
|
||||||
|
.bind(code)
|
||||||
|
.bind(1)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
DatabasePool::Sqlite(pool) => {
|
||||||
|
// First check if the exact combination exists
|
||||||
|
let exists = sqlx::query_scalar::<_, bool>(
|
||||||
|
"SELECT EXISTS(
|
||||||
|
SELECT 1 FROM links
|
||||||
|
WHERE original_url = ?1
|
||||||
|
AND short_code = ?2
|
||||||
|
)",
|
||||||
|
)
|
||||||
|
.bind(url)
|
||||||
|
.bind(code)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Only insert if the exact combination doesn't exist
|
||||||
|
if !exists {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO links (original_url, short_code, user_id)
|
||||||
|
VALUES (?1, ?2, ?3)",
|
||||||
|
)
|
||||||
|
.bind(url)
|
||||||
|
.bind(code)
|
||||||
|
.bind(1)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
info!("Created initial link: {} -> {} for user_id: 1", code, url);
|
||||||
|
} else {
|
||||||
|
info!("Skipped existing link: {} -> {} for user_id: 1", code, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_admin_user(pool: &DatabasePool, email: &str, password: &str) -> Result<()> {
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{rand_core::OsRng, SaltString},
|
||||||
|
Argon2, PasswordHasher,
|
||||||
|
};
|
||||||
|
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let password_hash = argon2
|
||||||
|
.hash_password(password.as_bytes(), &salt)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Password hashing error: {}", e))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
match pool {
|
||||||
|
DatabasePool::Postgres(pool) => {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO users (email, password_hash)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (email) DO NOTHING",
|
||||||
|
)
|
||||||
|
.bind(email)
|
||||||
|
.bind(&password_hash)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
DatabasePool::Sqlite(pool) => {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT OR IGNORE INTO users (email, password_hash)
|
||||||
|
VALUES (?1, ?2)",
|
||||||
|
)
|
||||||
|
.bind(email)
|
||||||
|
.bind(&password_hash)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("Created admin user: {}", email);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// Load environment variables from .env file
|
// Load environment variables from .env file
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue