added creation of access token and table for refresh token
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 10m12s

This commit is contained in:
Dmitri 2026-04-28 20:36:54 +02:00
parent 534c6ebf2f
commit ff4c9eee87
Signed by: kanopo
GPG Key ID: 759ADD40E3132AC7
17 changed files with 550 additions and 10 deletions

View File

@ -0,0 +1,52 @@
{
"db_name": "PostgreSQL",
"query": "select * from refresh_tokens where token_hash = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "user_id",
"type_info": "Uuid"
},
{
"ordinal": 2,
"name": "token_hash",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "expires_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "revoked_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "164c44b8f2c7c59eeaf4402be845f5788cc0e720ac0799a5457a893b2a070dda"
}

View File

@ -0,0 +1,54 @@
{
"db_name": "PostgreSQL",
"query": "insert into refresh_tokens (user_id, token_hash, expires_at) values ($1, $2, $3) returning *",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "user_id",
"type_info": "Uuid"
},
{
"ordinal": 2,
"name": "token_hash",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "expires_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "revoked_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid",
"Varchar",
"Timestamptz"
]
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "784522f0b0c26f78d25290e4cdb8058e68866905cd194b3f485d7563acba884c"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "update refresh_tokens set revoked_at = now() where id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "cba04640f6d764123bfb0a857498001bdf0ed3c9fe66e410ccd2ef66d5e2b35a"
}

266
Cargo.lock generated
View File

@ -26,6 +26,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "argon2" name = "argon2"
version = "0.5.3" version = "0.5.3"
@ -191,7 +197,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde",
"wasm-bindgen",
"windows-link", "windows-link",
] ]
@ -494,6 +503,19 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
@ -745,6 +767,12 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.1.0" version = "1.1.0"
@ -774,6 +802,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.17.0", "hashbrown 0.17.0",
"serde",
"serde_core",
] ]
[[package]] [[package]]
@ -792,6 +822,23 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "jsonwebtoken"
version = "10.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1"
dependencies = [
"base64",
"getrandom 0.2.17",
"js-sys",
"pem",
"rand",
"serde",
"serde_json",
"signature",
"simple_asn1",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -801,6 +848,12 @@ dependencies = [
"spin", "spin",
] ]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.186" version = "0.2.186"
@ -913,6 +966,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-bigint-dig" name = "num-bigint-dig"
version = "0.8.6" version = "0.8.6"
@ -1011,6 +1074,16 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64",
"serde_core",
]
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "0.7.0" version = "0.7.0"
@ -1089,6 +1162,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@ -1107,6 +1190,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.6" version = "0.8.6"
@ -1134,7 +1223,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.17",
] ]
[[package]] [[package]]
@ -1178,7 +1267,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"argon2", "argon2",
"axum", "axum",
"chrono",
"dotenvy", "dotenvy",
"jsonwebtoken",
"serde", "serde",
"serde_json", "serde_json",
"sqlx", "sqlx",
@ -1188,6 +1279,7 @@ dependencies = [
"tracing-appender", "tracing-appender",
"tracing-subscriber", "tracing-subscriber",
"tracing-tree", "tracing-tree",
"uuid",
] ]
[[package]] [[package]]
@ -1228,6 +1320,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@ -1351,6 +1449,18 @@ dependencies = [
"rand_core", "rand_core",
] ]
[[package]]
name = "simple_asn1"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@ -1932,6 +2042,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@ -1956,7 +2072,9 @@ version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
dependencies = [ dependencies = [
"getrandom 0.4.2",
"js-sys", "js-sys",
"serde_core",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1984,6 +2102,24 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
]
[[package]] [[package]]
name = "wasite" name = "wasite"
version = "0.1.0" version = "0.1.0"
@ -2035,6 +2171,40 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.1" version = "1.6.1"
@ -2179,6 +2349,100 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]] [[package]]
name = "writeable" name = "writeable"
version = "0.6.3" version = "0.6.3"

View File

@ -16,3 +16,6 @@ thiserror = "2"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
argon2 = "0.5.3" argon2 = "0.5.3"
jsonwebtoken = { version = "10.3.0", features = ["rand"] }
chrono = { version = "0.4.44", features = ["serde"] }
uuid = { version = "1.23.1", features = ["serde", "v4"] }

View File

@ -0,0 +1,10 @@
create table refresh_tokens (
id uuid primary key default uuidv4(),
user_id uuid not null references users(id) on delete cascade,
token_hash varchar(255) not null, -- SHA-256 hash of the plain token
expires_at timestamptz not null,
created_at timestamptz not null default now(),
revoked_at timestamptz,
constraint unique_token_hash unique (token_hash)
);

View File

@ -29,6 +29,7 @@ pub struct Config {
pub db_url: String, pub db_url: String,
pub app_env: AppEnv, pub app_env: AppEnv,
pub socket_address: String, pub socket_address: String,
pub jwt_secret: String,
} }
impl Config { impl Config {
@ -38,6 +39,7 @@ impl Config {
db_url: env::var("DATABASE_URL")?, db_url: env::var("DATABASE_URL")?,
socket_address: env::var("SOCKET_ADDRESS")?, socket_address: env::var("SOCKET_ADDRESS")?,
app_env: AppEnv::from_env()?, app_env: AppEnv::from_env()?,
jwt_secret: env::var("JWT_SECRET")?,
}) })
} }
} }

View File

@ -1 +1,2 @@
pub mod refresh_token;
pub mod user; pub mod user;

View File

@ -0,0 +1,14 @@
use sqlx::types::{
Uuid,
chrono::{DateTime, Utc},
};
#[derive(Debug)]
pub struct RefreshToken {
pub id: Uuid,
pub user_id: Uuid,
pub token_hash: String,
pub expires_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub revoked_at: Option<DateTime<Utc>>,
}

View File

@ -1 +1,2 @@
pub mod refresh_token_repository;
pub mod user_repository; pub mod user_repository;

View File

@ -0,0 +1,59 @@
use sqlx::{
Executor, Postgres,
types::{
Uuid,
chrono::{DateTime, Utc},
},
};
use crate::{db::model::refresh_token::RefreshToken, errors::AppError};
pub async fn create_refresh_token<'e, E>(
executor: E,
user_id: Uuid,
token_hash: String,
expires_at: DateTime<Utc>,
) -> Result<RefreshToken, AppError>
where
E: Executor<'e, Database = Postgres>,
{
sqlx::query_as!(
RefreshToken,
"insert into refresh_tokens (user_id, token_hash, expires_at) values ($1, $2, $3) returning *",
user_id,
token_hash,
expires_at
).fetch_one(executor)
.await
.map_err(AppError::from)
}
pub async fn find_by_hash<'e, E>(
executor: E,
token_hash: &str,
) -> Result<Option<RefreshToken>, AppError>
where
E: Executor<'e, Database = Postgres>,
{
sqlx::query_as!(
RefreshToken,
"select * from refresh_tokens where token_hash = $1",
token_hash
)
.fetch_optional(executor)
.await
.map_err(AppError::from)
}
pub async fn revoke<'e, E>(executor: E, id: Uuid) -> Result<(), AppError>
where
E: Executor<'e, Database = Postgres>,
{
sqlx::query!(
"update refresh_tokens set revoked_at = now() where id = $1",
id
)
.execute(executor)
.await
.map_err(AppError::from)?;
Ok(())
}

View File

@ -1,33 +1,51 @@
use sqlx::{PgPool, types::Uuid}; use sqlx::{Executor, PgPool, Postgres, types::Uuid};
use crate::{db::model::user::User, errors::AppError}; use crate::{db::model::user::User, errors::AppError};
pub async fn create_user(pool: &PgPool, email: String, password: String) -> Result<User, AppError> { pub async fn create_user<'e, E>(
executor: E,
email: String,
password: String,
) -> Result<User, AppError>
where
E: Executor<'e, Database = Postgres>,
{
let user = sqlx::query_as!( let user = sqlx::query_as!(
User, User,
"insert into users (email, password) values ($1, $2) returning *", "insert into users (email, password) values ($1, $2) returning *",
email, email,
password password
) )
.fetch_one(pool) .fetch_one(executor)
.await .await
.map_err(AppError::from)?; .map_err(AppError::from)?;
Ok(user) Ok(user)
} }
pub async fn get_user_by_email(pool: &PgPool, email: String) -> Result<Option<User>, AppError> { /*
*And these two call patterns both work:
- Pool: user_repo::create_user(&state.db, ...) E = &'static PgPool
- Transaction: user_repo::create_user(&mut *tx, ...) E = &mut PgConnection
*/
pub async fn get_user_by_email<'e, E>(executor: E, email: &str) -> Result<Option<User>, AppError>
where
E: Executor<'e, Database = Postgres>,
{
let user = sqlx::query_as!(User, "select * from users where email=$1", email) let user = sqlx::query_as!(User, "select * from users where email=$1", email)
.fetch_optional(pool) .fetch_optional(executor)
.await .await
.map_err(AppError::from)?; .map_err(AppError::from)?;
Ok(user) Ok(user)
} }
pub async fn get_user_by_id(pool: &PgPool, id: Uuid) -> Result<Option<User>, AppError> { pub async fn get_user_by_id<'e, E>(executor: E, id: Uuid) -> Result<Option<User>, AppError>
where
E: Executor<'e, Database = Postgres>,
{
let user = sqlx::query_as!(User, "select * from users where id=$1", id) let user = sqlx::query_as!(User, "select * from users where id=$1", id)
.fetch_optional(pool) .fetch_optional(executor)
.await .await
.map_err(AppError::from)?; .map_err(AppError::from)?;

View File

@ -4,7 +4,10 @@ use sqlx::PgPool;
use crate::{config, controller, errors::AppError, logging, state::AppState}; use crate::{config, controller, errors::AppError, logging, state::AppState};
pub async fn init(cfg: &config::Config, db: PgPool) -> Result<(), AppError> { pub async fn init(cfg: &config::Config, db: PgPool) -> Result<(), AppError> {
let state = AppState { db }; let state = AppState {
db,
jwt_secret: cfg.jwt_secret.clone(),
};
let app = Router::new().merge(controller::router()).with_state(state); let app = Router::new().merge(controller::router()).with_state(state);
let listener = tokio::net::TcpListener::bind(&cfg.socket_address) let listener = tokio::net::TcpListener::bind(&cfg.socket_address)

View File

@ -5,6 +5,7 @@ use crate::db::repository::user_repository;
use crate::errors::AppError; use crate::errors::AppError;
use crate::state::AppState; use crate::state::AppState;
use crate::utils::hash; use crate::utils::hash;
use crate::utils::jwt::generate_access_token;
pub async fn login(state: &AppState, req: LoginRequest) -> Result<Json<AuthResponse>, AppError> { pub async fn login(state: &AppState, req: LoginRequest) -> Result<Json<AuthResponse>, AppError> {
todo!() todo!()
@ -13,7 +14,16 @@ pub async fn register(
state: &AppState, state: &AppState,
req: RegisterRequest, req: RegisterRequest,
) -> Result<Json<AuthResponse>, AppError> { ) -> Result<Json<AuthResponse>, AppError> {
let mut tx = state.db.begin().await?;
let user = user_repository::get_user_by_email(&mut *tx, &req.email).await?;
if user.is_some() {
// user already registered
return Err(AppError::Internal);
}
let h = hash::hash(&req.password)?; let h = hash::hash(&req.password)?;
let user = user_repository::create_user(&state.db, req.email, h); let user = user_repository::create_user(&mut *tx, req.email, h).await?;
let access_token = generate_access_token(user.id, &state.jwt_secret)?;
tx.commit().await?;
todo!("generate jwt and refresh token") todo!("generate jwt and refresh token")
} }

View File

@ -3,4 +3,5 @@ use sqlx::PgPool;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
pub db: PgPool, pub db: PgPool,
pub jwt_secret: String,
} }

33
src/utils/jwt.rs Normal file
View File

@ -0,0 +1,33 @@
use chrono::{Duration, Utc};
use jsonwebtoken::{EncodingKey, Header, encode};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::errors::AppError;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: Uuid,
pub iat: i64,
pub exp: i64,
pub jti: Uuid,
}
pub fn generate_access_token(user_id: Uuid, secret: &str) -> Result<String, AppError> {
let now = Utc::now();
let expires_at = now + Duration::minutes(15);
let claims = Claims {
sub: user_id,
iat: now.timestamp(),
exp: expires_at.timestamp(),
jti: Uuid::new_v4(),
};
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.map_err(|_| AppError::Internal)
}

View File

@ -1 +1,2 @@
pub mod hash; pub mod hash;
pub mod jwt;