【Rust】hashmapのキーの存在チェック

map.get(&target) != None でチェックできる。

    let mut holder: HashMap<String, String> = HashMap::new();

    holder.insert("yamada".to_string(), "A".to_string());
    holder.insert("sato".to_string(), "B".to_string());
    holder.insert("tanaka".to_string(), "C".to_string());

    for (name, item) in &holder {
        println!("{}:{}", name, item);
    }

    let target = "yamada".to_string();
    if holder.get(&target) != None {
        println!("{}", holder["yamada"]);
    }

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33s
Running `target/debug/my_web_app`
yamada:A
tanaka:C
sato:B
A

pythonのように target in map ではチェックできない。
なるほど…

【Rust】トランザクションをbase64でエンコード、デコード

hello worldで挙動を確認します。

let str:String = "hello world".to_string();
println!("{}", BASE64_STANDARD.encode(str.clone()));

let b:String = "aGVsbG8gd29ybGQ=".to_string();
println!("{}", base64_decode(&b));

pub fn base64_decode_bytes(b64str: &str) -> Vec {
let t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut table: [u8; 256] = [0; 256];
for (i, v) in t.as_bytes().iter().enumerate() {
table[*v as usize] = i as u8;
}
let b64 = String::from(b64str).replace("\r", "").replace("\n", "");
let b64bytes = b64.as_bytes();
let mut result: Vec = vec![];
let cnt = b64bytes.len() / 4;
for i in 0..cnt {
let i0 = b64bytes[i*4+0];
let i1 = b64bytes[i*4+1];
let i2 = b64bytes[i*4+2];
let i3 = b64bytes[i*4+3];
let c0 = table[i0 as usize] as usize;
let c1 = table[i1 as usize] as usize;
let c2 = table[i2 as usize] as usize;
let c3 = table[i3 as usize] as usize;
let b24 = (c0 << 18) | (c1 << 12) | (c2 << 6) | (c3 << 0); let b0 = ((b24 >> 16) & 0xFF) as u8;
let b1 = ((b24 >> 8) & 0xFF) as u8;
let b2 = ((b24 >> 0) & 0xFF) as u8;
result.push(b0);
if i2 as char != '=' { result.push(b1); }
if i3 as char != '=' { result.push(b2); }
}
result
}
pub fn base64_decode(b64str: &str) -> String {
String::from_utf8(base64_decode_bytes(b64str)).unwrap()
}
[/cpde]
aGVsbG8gd29ybGQ=
hello world

上手くいっているようです。
これをトランザクションでやります。

let transaction1 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"hello world".to_string(),nft_origin:"".to_string()};

println!("{}", BASE64_STANDARD.encode(serde_json::to_vec(&transaction1.clone()).unwrap()));

let str:String = "eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyNzo1MS4wNTA0MjYxMDMgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9".to_string();
println!("{}", base64_decode(&str));
[/cpde]

eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyOTo0Ni42NzgzNDU3OTQgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9
{"time":"2025-01-17 08:27:51.050426103 UTC","sender":"047683c00f6z","receiver":"DyH5SHvezz","amount":0,"nft_data":"hello world","nft_origin":""}
SignedTransaction { time: "2025-01-17 08:29:46.678345794 UTC", sender: "047683c00f6z", receiver: "DyH5SHvezz", amount: 0, nft_data: "hello world", nft_origin: "", signature: "568082531D765278ACF8999B94BAA7C621C538B00857DAC165208DB8C73FE4D4CB8FDBC742A4A47BC1D0B421A543134D869B2EC1AC8CEA0DDE902D39B034EA84" }

よし、O~K

【Rust】axumでログアウトの処理

async fn handle_logout()-> impl IntoResponse{

    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("data", "hoge");

    let output = tera.render("index.html", &context);
    let cookie:String = format!("session_token=_");
    let headers = AppendHeaders(vec![("Set-Cookie", cookie)]);
    (headers, axum::response::Html(output.unwrap()))
}

特にコメントなし。
さー次行こう^^

【Rust】axumでセッション(cookie)の有無でルーティングを分ける

cookieを判定するmiddlewareを作成し、privateなページのみミドルウェアをつけて、publicなページにはつけない。
該当のsession_tokenがない場合は、Redirectでpublicのページにリダイレクトさせる。

async fn main() {

    let public_router = Router::new()
        .route("/setcookie", get(handle_setcookie))
        .route("/public", get(handle_public));

    let private_router = Router::new()
        .route("/test", get(handle_index))
        .route("/private", get(handle_private))
        .layer(middleware::from_fn(my_middleware));

    let app = Router::new()
        .nest("/", public_router)
        .nest("/", private_router);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
//

    if session_token == None {
        Redirect::permanent("/public").into_response()
    } else {
        let mut res = next.run(req).await;
        res
    }

おおおおおおお、昨日長時間悶絶してただけあって、ここは割と簡単に上手くいった。

【Rust】axumでcookieを取得する

routeにmiddlewareをくっつけます。
Requestのheadersからcookieを取得します。

#[tokio::main]
async fn main() {
    let app = axum::Router::new()
        .route("/", axum::routing::get(handle_index))
        .layer(middleware::from_fn(my_middleware));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
//
async fn my_middleware(req: Request,
    next: Next,) -> impl IntoResponse {

    let mut session_token = req
        .headers()
        .get_all("Cookie")
        .iter()
        .filter_map(|cookie| {
            cookie
                .to_str()
                .ok()
                .and_then(|cookie| cookie.parse::<cookie::Cookie>().ok())
        })
        .find_map(|cookie| {
            (cookie.name() == "hoge".to_string()).then(move || cookie.value().to_owned())
        });
    println!("{:?}", &session_token);

    let cookies = req.headers().get("cookie");
    println!("{:?}", &cookies);

    let mut res = next.run(req).await;
    res
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.60s
Running `target/debug/axum`
Some(“value”)
Some(“hoge=value”)
hoge=value; Max-Age=600

ほう、なるほど。middlewareにリダイレクトを追加したい。

【Rust】セッションをcookieに保存したい

AppendHeadersに中々辿り着けず、layerを堂々巡りした挙句、chromのcookieの設定などをミスって、これ作るのに丸2日かかりました。。。シクシク、泣きたい。

async fn handle_index()-> impl IntoResponse {
    
    let mut random = ChaCha8Rng::seed_from_u64(OsRng.next_u64());
    let mut u128_pool = [0u8; 16];
    random.fill_bytes(&mut u128_pool);
    let session_token = u128::from_le_bytes(u128_pool);
    println!("{:?}", &session_token);

    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("title", "Index page");

    let output = tera.render("index.html", &context);
    let cookie:String = format!("session_token={}; Max-Age=600", &session_token);
    let headers = AppendHeaders(vec![("Set-Cookie", cookie)]);
    (headers, axum::response::Html(output.unwrap()))
}

psqlにセッションテーブルを作って、ログインしたタイミングでセッションテーブルにもsessionのvalueを保存するようにする。

【Rust】ChaCha8Rngでセッションを作りたい

use rand_core::OsRng;
use rand_core::RngCore;
use rand_chacha::ChaCha8Rng;
use rand_chacha::rand_core::SeedableRng;


fn main(){
    let mut random = ChaCha8Rng::seed_from_u64(OsRng.next_u64());

    let mut u128_pool = [0u8; 16];
    random.fill_bytes(&mut u128_pool);

    let session_token = u128::from_le_bytes(u128_pool);
    println!("{:?}", session_token);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/sample`
68291255163114295989027254289217915346

いまいち意味わかってないけどすげえ…

【Rust】Axumでなんとかログイン機能を作りたい(その3)

axumでユーザの登録画面、insert文を作っていきます。
L formで送られてきたパスワードをハッシュ化して、usrnameと一緒に保存します。

async fn handle_save(form: Form<SignUp>)-> axum::response::Html<String> {
    let register: SignUp = form.0;
    let password_hash = hashing_password(&register.password);

    println!("{}", &password_hash);

    let _ = db_save(&register.username.to_string(), &password_hash).await.unwrap();

    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("title", "Index page");

    let output = tera.render("index.html", &context);
    axum::response::Html(output.unwrap())
}

async fn db_save(name: &String, password: &String) -> Result<(), Box<dyn std::error::Error>> {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres password=password", NoTls).await?;

    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
    client.execute(
        "INSERT INTO users (username, password) VALUES ($1, $2)",
        &[&name, &password],
    ).await?;

    for row in client.query("SELECT id, username, password From users", &[]).await? {
        let id: i32 = row.get(0);
        let name: String = row.get(1);
        let password: String = row.get(2);

        println!("{} {} {}", id, name, password);
    }
    Ok(())
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.95s
Running `target/debug/login`
$2b$08$OxMrzm88ag/iPtKahE3kveHNmmUI5uAuYGuY.lcCybGGqN8cYx/km
1 hpscript $2b$08$bKVELv/My0WaKvoyTltoLe3pOp7VJ4UxtxuA3C5qurnAblnqXbcfi
2 yamada $2b$08$OxMrzm88ag/iPtKahE3kveHNmmUI5uAuYGuY.lcCybGGqN8cYx/km

ここまでは割と簡単なんだけど、問題はここからなんだよな〜

【Rust】Axumでなんとかログイン機能を作りたい(その2)

postgres=# INSERT INTO users (username, password) VALUES (‘hpscript’, ‘$2b$08$bKVELv/My0WaKvoyTltoLe3pOp7VJ4UxtxuA3C5qurnAblnqXbcfi’);
INSERT 0 1
postgres=# select * from users;
id | username | password
—-+———-+————————————————————–
1 | hpscript | $2b$08$bKVELv/My0WaKvoyTltoLe3pOp7VJ4UxtxuA3C5qurnAblnqXbcfi

DBに入っているパスワードハッシュ値と、Postされてきたpasswordの一致を検証する。
asyncでpsqlを使う場合は、postgresではなく、tokio_postgres出ないとエラーになる。
参考: https://docs.rs/tokio-postgres/latest/tokio_postgres/

async fn handle_signup(form: Form<SignUp>)-> axum::response::Html<String> {
    let signup: SignUp = form.0;

    let hash = db_check(&signup.username.to_string()).await.unwrap();
    println!("{}", &hash);

    let password = &signup.password;
    println!("{}", verify(password, &hash));

    println!("{:?}", signup);

    // 省略
}
fn verify(password: &String, hash: &String) -> bool {
    bcrypt::verify(password, hash)
}

async fn db_check(name: &String) -> Result<String, Box<dyn std::error::Error>> {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres password=password", NoTls).await?;

    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
    let row = client.query("SELECT * From users where username=$1", &[&name]).await?;
    let hash: String = row[0].get(2);
    println!("{}",hash);
    Ok(hash)
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.44s
Running `target/debug/login`
$2b$08$bKVELv/My0WaKvoyTltoLe3pOp7VJ4UxtxuA3C5qurnAblnqXbcfi
$2b$08$bKVELv/My0WaKvoyTltoLe3pOp7VJ4UxtxuA3C5qurnAblnqXbcfi
true
SignUp { username: “hpscript”, password: “asdf1234” }

よしよしよーし。セッションは後からやるとして、先にpsqlへのinsertとregister画面から作ります。

【Rust】Axumでなんとかログイン機能を作りたい(その1)

### usersテーブルの作成

sudo -u postgres psql

CREATE TABLE users (
	id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
	username text NOT NULL UNIQUE,
	password text NOT NULL
);

axumでformでpostされたデータを受け取ります。

use axum::{Router, routing::get, routing::post, extract::Form};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Clone, Debug)]
struct SignUp {
    username: String,
    password: String,
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", axum::routing::get(handle_index))
        .route("/signup", axum::routing::post(handle_signup));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn handle_index()-> axum::response::Html<String> {
    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("data", "hoge");

    let output = tera.render("index.html", &context);
    axum::response::Html(output.unwrap())
}

async fn handle_signup(form: Form<SignUp>)-> axum::response::Html<String> {
    let signup: SignUp = form.0;
    println!("{:?}", signup);

    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("data", "hoge");

    let output = tera.render("index.html", &context);
    axum::response::Html(output.unwrap())
}

コマンドラインからPostします。
$ curl -X POST -d ‘username=山田太郎’ -d ‘password=asdf1234’ 127.0.0.1:3000/signup

Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.65s
Running `target/debug/login`
SignUp { username: “山田太郎”, password: “asdf1234” }

なるほど、なんとかここまでできた。ミドルウェアのところと、セッションの箇所をどうするか悩ましいが、まぁよしとしよう。