Skip to content

Commit dfbf463

Browse files
committed
feat: pass username header
1 parent 86b7388 commit dfbf463

7 files changed

Lines changed: 56 additions & 9 deletions

File tree

USAGE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ For each service, Kiwi creates **a PostgreSQL database with credentials** and **
4646
- `KIWI_REDIS_URI`, with the URI of the Redis instance your service can access, already including username and password
4747
- `KIWI_REDIS_PREFIX`, with the prefix of the Redis keys your service can access inside the instance
4848

49+
Moreover, each HTTP request is added the following headers
50+
51+
- `X-Kiwi-User-Id`, containing the ID of the user in case they're authenticated. The header is omitted otherwise.
52+
- `X-Kiwi-Username`, containing the username of the user in case they're authenticated. The header is omitted otherwise.
53+
4954
### CI and Deployment 🧑‍🚀
5055

5156
> [!NOTE]

backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ zxcvbn = "3.1.0"
4141

4242
[lints.clippy]
4343
uninlined_format_args = "allow"
44+
too_many_arguments = "allow"

backend/src/constants.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub static ACCESS_TOKEN_COOKIE_NAME: &str = "__kiwi_access_token";
22
pub static REFRESH_TOKEN_COOKIE_NAME: &str = "__kiwi_refresh_token";
33
pub static LOGOUT_REFRESH_TOKEN_COPY_NAME: &str = "__kiwi_logout_refresh_token_copy";
44
pub static KIWI_USER_ID_HEADER_NAME: &str = "X-Kiwi-User-Id";
5+
pub static KIWI_USERNAME_HEADER_NAME: &str = "X-Kiwi-Username";

backend/src/managers/redis/models.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub trait RedisItem: Sized {
3232
pub struct RedisAccessToken {
3333
pub access_token: String,
3434
pub user_id: i64,
35+
pub username: String,
3536
pub sealing_key: String,
3637
pub role: UserRole,
3738
}
@@ -42,7 +43,10 @@ impl RedisItem for RedisAccessToken {
4243
}
4344

4445
fn to_redis_value(&self) -> String {
45-
format!("{}:{}:{}", self.user_id, self.sealing_key, self.role)
46+
format!(
47+
"{}:{}:{}:{}",
48+
self.user_id, self.username, self.sealing_key, self.role
49+
)
4650
}
4751

4852
fn get_expiration(&self) -> Option<Expiration> {
@@ -63,13 +67,15 @@ impl RedisItem for RedisAccessToken {
6367
.ok_or(Error::serialisation())?
6468
.parse()
6569
.map_err(|_| Error::serialisation())?;
66-
let sealing_key = values.get(1).ok_or(Error::serialisation())?.clone();
67-
let role_raw = values.get(2).ok_or(Error::serialisation())?.clone();
70+
let username = values.get(1).ok_or(Error::serialisation())?.clone();
71+
let sealing_key = values.get(2).ok_or(Error::serialisation())?.clone();
72+
let role_raw = values.get(3).ok_or(Error::serialisation())?.clone();
6873
let role = UserRole::from_str(&role_raw)?;
6974

7075
Ok(RedisAccessToken {
7176
access_token: consumed_key,
7277
user_id,
78+
username,
7379
sealing_key,
7480
role,
7581
})
@@ -78,6 +84,7 @@ impl RedisItem for RedisAccessToken {
7884

7985
pub struct RedisActiveRefreshToken {
8086
pub user_id: i64,
87+
pub username: String,
8188
pub sealing_key: String,
8289
pub role: UserRole,
8390
}
@@ -105,7 +112,10 @@ impl RedisItem for RedisRefreshToken {
105112
fn to_redis_value(&self) -> String {
106113
match &self.kind {
107114
RedisRefreshTokenKind::Active(data) => {
108-
format!("active:{}:{}:{}", data.user_id, data.sealing_key, data.role,)
115+
format!(
116+
"active:{}:{}:{}:{}",
117+
data.user_id, data.username, data.sealing_key, data.role,
118+
)
109119
}
110120
RedisRefreshTokenKind::Refreshed(data) => format!(
111121
"refreshed:{}:{}",
@@ -138,13 +148,15 @@ impl RedisItem for RedisRefreshToken {
138148
"active" => {
139149
let raw_user_id = values.get(1).ok_or(Error::serialisation())?;
140150
let user_id: i64 = raw_user_id.parse().map_err(|_| Error::serialisation())?;
141-
let sealing_key = values.get(2).ok_or(Error::serialisation())?.to_owned();
142-
let role_raw = values.get(3).ok_or(Error::serialisation())?.clone();
151+
let username = values.get(2).ok_or(Error::serialisation())?.to_owned();
152+
let sealing_key = values.get(3).ok_or(Error::serialisation())?.to_owned();
153+
let role_raw = values.get(4).ok_or(Error::serialisation())?.clone();
143154
let role = UserRole::from_str(&role_raw)?;
144155
Ok(RedisRefreshToken {
145156
refresh_token: consumed_key,
146157
kind: RedisRefreshTokenKind::Active(RedisActiveRefreshToken {
147158
user_id,
159+
username,
148160
sealing_key,
149161
role,
150162
}),

backend/src/managers/redis/queries.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@ impl RedisManager {
1717
access_token: &str,
1818
refresh_token: &str,
1919
user_id: i64,
20+
username: &str,
2021
sealing_key: &str,
2122
role: &UserRole,
2223
) -> Result<(), Error> {
2324
let access_token_item = RedisAccessToken {
2425
access_token: access_token.to_string(),
2526
user_id,
27+
username: username.to_string(),
2628
sealing_key: sealing_key.to_string(),
2729
role: role.clone(),
2830
};
2931
let refresh_token_item = RedisRefreshToken {
3032
refresh_token: refresh_token.to_string(),
3133
kind: RedisRefreshTokenKind::Active(RedisActiveRefreshToken {
3234
user_id,
35+
username: username.to_string(),
3336
sealing_key: sealing_key.to_string(),
3437
role: role.clone(),
3538
}),
@@ -66,6 +69,7 @@ impl RedisManager {
6669
let key = RedisAccessToken {
6770
access_token: access_token.to_string(),
6871
user_id: 0,
72+
username: String::new(),
6973
sealing_key: String::new(),
7074
role: UserRole::Customer,
7175
}
@@ -88,6 +92,7 @@ impl RedisManager {
8892
refresh_token: refresh_token.to_string(),
8993
kind: RedisRefreshTokenKind::Active(RedisActiveRefreshToken {
9094
user_id: 0,
95+
username: String::new(),
9196
sealing_key: String::new(),
9297
role: UserRole::Customer,
9398
}),
@@ -109,6 +114,7 @@ impl RedisManager {
109114
fresh_access_token: &str,
110115
fresh_refresh_token: &str,
111116
user_id: i64,
117+
username: &str,
112118
sealing_key: &str,
113119
role: &UserRole,
114120
) -> Result<(), Error> {
@@ -122,13 +128,15 @@ impl RedisManager {
122128
let access_token_item = RedisAccessToken {
123129
access_token: fresh_access_token.to_string(),
124130
user_id,
131+
username: username.to_string(),
125132
sealing_key: sealing_key.to_string(),
126133
role: role.clone(),
127134
};
128135
let refresh_token_item = RedisRefreshToken {
129136
refresh_token: fresh_refresh_token.to_string(),
130137
kind: RedisRefreshTokenKind::Active(RedisActiveRefreshToken {
131138
user_id,
139+
username: username.to_string(),
132140
sealing_key: sealing_key.to_string(),
133141
role: role.clone(),
134142
}),
@@ -172,6 +180,7 @@ impl RedisManager {
172180
refresh_token: refresh_token.to_string(),
173181
kind: RedisRefreshTokenKind::Active(RedisActiveRefreshToken {
174182
user_id: 0,
183+
username: String::new(),
175184
sealing_key: String::new(),
176185
role: UserRole::Customer,
177186
}),

backend/src/middlewares/authentication.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use axum_extra::extract::CookieJar;
99
use urlencoding::encode;
1010

1111
use crate::{
12-
constants::{ACCESS_TOKEN_COOKIE_NAME, KIWI_USER_ID_HEADER_NAME},
12+
constants::{ACCESS_TOKEN_COOKIE_NAME, KIWI_USER_ID_HEADER_NAME, KIWI_USERNAME_HEADER_NAME},
1313
error::Error,
1414
extractors::{Domain, FullOriginalUri},
1515
managers::redis::RedisManager,
@@ -26,6 +26,7 @@ pub async fn authentication_middleware(
2626
) -> Response {
2727
// Remove any abused auth header
2828
request.headers_mut().remove(KIWI_USER_ID_HEADER_NAME);
29+
request.headers_mut().remove(KIWI_USERNAME_HEADER_NAME);
2930

3031
let service = request
3132
.uri()
@@ -59,10 +60,16 @@ pub async fn authentication_middleware(
5960

6061
if !access_token_item.role.has_permissions(&required_role) {
6162
Error::bad_permissions().into_response()
62-
} else if let Ok(user_id_header_value) = HeaderValue::from_str(&user_id_string) {
63+
} else if let (Ok(user_id_header_value), Ok(username_header_value)) = (
64+
HeaderValue::from_str(&user_id_string),
65+
HeaderValue::from_str(&access_token_item.username),
66+
) {
6367
request
6468
.headers_mut()
6569
.append(KIWI_USER_ID_HEADER_NAME, user_id_header_value);
70+
request
71+
.headers_mut()
72+
.append(KIWI_USERNAME_HEADER_NAME, username_header_value);
6673
next.run(request).await
6774
} else {
6875
Error::serialisation().into_response()

backend/src/routes/auth/api/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ async fn create_user(
7070
domain,
7171
redis_manager,
7272
user_data.id,
73+
user_data.username,
7374
sealing_key,
7475
user_data.role,
7576
None,
@@ -103,6 +104,7 @@ async fn login(
103104
domain,
104105
redis_manager,
105106
user_data.id,
107+
user_data.username,
106108
sealing_key,
107109
user_data.role,
108110
None,
@@ -170,6 +172,7 @@ async fn refresh_credentials(
170172
domain,
171173
redis_manager,
172174
data.user_id,
175+
data.username,
173176
data.sealing_key,
174177
data.role,
175178
Some(refresh_token),
@@ -254,6 +257,7 @@ async fn generate_and_store_tokens(
254257
domain: String,
255258
redis_manager: RedisManager,
256259
user_id: i64,
260+
username: String,
257261
sealing_key: String,
258262
role: UserRole,
259263
old_refresh_token: Option<String>,
@@ -268,13 +272,21 @@ async fn generate_and_store_tokens(
268272
&access_token,
269273
&refresh_token,
270274
user_id,
275+
&username,
271276
&sealing_key,
272277
&role,
273278
)
274279
.await?;
275280
} else {
276281
redis_manager
277-
.store_active_auth_tokens(&access_token, &refresh_token, user_id, &sealing_key, &role)
282+
.store_active_auth_tokens(
283+
&access_token,
284+
&refresh_token,
285+
user_id,
286+
&username,
287+
&sealing_key,
288+
&role,
289+
)
278290
.await?;
279291
}
280292

0 commit comments

Comments
 (0)