Skip to content

Commit 83eedfc

Browse files
bors-voyager[bot]Josh Leeb-du Toit
and
Josh Leeb-du Toit
committed
Merge #1567
1567: Mark API tokens as revoked r=carols10cents a=joshleeb With this PR, when a user deletes an API token, rather than deleting it from the database, it will instead be marked as `revoked`. That means that this PR also adds a migration to add the column `revoked` to the `api_tokens` table. Ref. #1548 (Task 1) Co-authored-by: Josh Leeb-du Toit <[email protected]>
2 parents 1b743ca + 359ec94 commit 83eedfc

File tree

8 files changed

+57
-3
lines changed

8 files changed

+57
-3
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE api_tokens DROP COLUMN revoked;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE api_tokens ADD COLUMN revoked BOOLEAN NOT NULL DEFAULT 'f';

src/controllers/token.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use views::EncodableApiTokenWithToken;
1212
/// Handles the `GET /me/tokens` route.
1313
pub fn list(req: &mut dyn Request) -> CargoResult<Response> {
1414
let tokens = ApiToken::belonging_to(req.user()?)
15+
.filter(api_tokens::revoked.eq(false))
1516
.order(api_tokens::created_at.desc())
1617
.load(&*req.db_conn()?)?;
1718
#[derive(Serialize)]
@@ -94,7 +95,9 @@ pub fn revoke(req: &mut dyn Request) -> CargoResult<Response> {
9495
.parse::<i32>()
9596
.map_err(|e| bad_request(&format!("invalid token id: {:?}", e)))?;
9697

97-
diesel::delete(ApiToken::belonging_to(req.user()?).find(id)).execute(&*req.db_conn()?)?;
98+
diesel::update(ApiToken::belonging_to(req.user()?).find(id))
99+
.set(api_tokens::revoked.eq(true))
100+
.execute(&*req.db_conn()?)?;
98101

99102
#[derive(Serialize)]
100103
struct R {}

src/models/token.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub struct ApiToken {
2121
pub created_at: NaiveDateTime,
2222
#[serde(with = "rfc3339::option")]
2323
pub last_used_at: Option<NaiveDateTime>,
24+
#[serde(skip)]
25+
pub revoked: bool,
2426
}
2527

2628
impl ApiToken {
@@ -40,6 +42,7 @@ impl ApiToken {
4042
id: self.id,
4143
name: self.name,
4244
token: self.token,
45+
revoked: self.revoked,
4346
created_at: self.created_at,
4447
last_used_at: self.last_used_at,
4548
}
@@ -58,6 +61,7 @@ mod tests {
5861
id: 12345,
5962
user_id: 23456,
6063
token: "".to_string(),
64+
revoked: false,
6165
name: "".to_string(),
6266
created_at: NaiveDate::from_ymd(2017, 1, 6).and_hms(14, 23, 11),
6367
last_used_at: Some(NaiveDate::from_ymd(2017, 1, 6).and_hms(14, 23, 12)),
@@ -81,6 +85,7 @@ mod tests {
8185
id: 12345,
8286
name: "".to_string(),
8387
token: "".to_string(),
88+
revoked: false,
8489
created_at: NaiveDate::from_ymd(2017, 1, 6).and_hms(14, 23, 11),
8590
last_used_at: Some(NaiveDate::from_ymd(2017, 1, 6).and_hms(14, 23, 12)),
8691
};

src/models/user.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,12 @@ impl User {
109109
/// Queries the database for a user with a certain `api_token` value.
110110
pub fn find_by_api_token(conn: &PgConnection, token_: &str) -> CargoResult<User> {
111111
use diesel::update;
112-
use schema::api_tokens::dsl::{api_tokens, last_used_at, token, user_id};
112+
use schema::api_tokens::dsl::{api_tokens, last_used_at, revoked, token, user_id};
113113
use schema::users::dsl::{id, users};
114-
let user_id_ = update(api_tokens.filter(token.eq(token_)))
114+
let tokens = api_tokens
115+
.filter(token.eq(token_))
116+
.filter(revoked.eq(false));
117+
let user_id_ = update(tokens)
115118
.set(last_used_at.eq(now.nullable()))
116119
.returning(user_id)
117120
.get_result::<i32>(conn)?;

src/schema.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ table! {
4545
///
4646
/// (Automatically generated by Diesel.)
4747
last_used_at -> Nullable<Timestamp>,
48+
/// The `revoked` column of the `api_tokens` table.
49+
///
50+
/// Its SQL type is `Bool`.
51+
///
52+
/// (Automatically generated by Diesel.)
53+
revoked -> Bool,
4854
}
4955
}
5056

src/tests/token.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::collections::HashSet;
33
use diesel::prelude::*;
44

55
use models::ApiToken;
6+
use schema::api_tokens;
67
use views::{EncodableApiTokenWithToken, EncodableMe};
78
use {user::UserShowPrivateResponse, RequestHelper, TestApp};
89

@@ -69,6 +70,37 @@ fn list_tokens() {
6970
);
7071
}
7172

73+
#[test]
74+
fn list_tokens_exclude_revoked() {
75+
let (app, _, user) = TestApp::init().with_user();
76+
let id = user.as_model().id;
77+
let tokens = app.db(|conn| {
78+
vec![
79+
t!(ApiToken::insert(conn, id, "bar")),
80+
t!(ApiToken::insert(conn, id, "baz")),
81+
]
82+
});
83+
84+
// List tokens expecting them all to be there.
85+
let json: ListResponse = user.get(URL).good();
86+
assert_eq!(json.api_tokens.len(), tokens.len());
87+
88+
// Revoke the first token.
89+
let _json: RevokedResponse = user
90+
.delete(&format!("/api/v1/me/tokens/{}", tokens[0].id))
91+
.good();
92+
93+
// Check that we now have one less token being listed.
94+
let json: ListResponse = user.get(URL).good();
95+
assert_eq!(json.api_tokens.len(), tokens.len() - 1);
96+
assert!(
97+
json.api_tokens
98+
.iter()
99+
.find(|token| token.name == tokens[0].name)
100+
.is_none()
101+
);
102+
}
103+
72104
#[test]
73105
fn create_token_logged_out() {
74106
let (_, anon) = TestApp::init().empty();
@@ -128,6 +160,7 @@ fn create_token_success() {
128160
assert_eq!(tokens.len(), 1);
129161
assert_eq!(tokens[0].name, "bar");
130162
assert_eq!(tokens[0].token, json.api_token.token);
163+
assert_eq!(tokens[0].revoked, false);
131164
assert_eq!(tokens[0].last_used_at, None);
132165
}
133166

@@ -218,6 +251,7 @@ fn revoke_token_success() {
218251
// List tokens no longer contains the token
219252
app.db(|conn| {
220253
let count = ApiToken::belonging_to(user.as_model())
254+
.filter(api_tokens::revoked.eq(false))
221255
.count()
222256
.get_result(conn);
223257
assert_eq!(count, Ok(0));

src/views/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ pub struct EncodableApiTokenWithToken {
144144
pub id: i32,
145145
pub name: String,
146146
pub token: String,
147+
pub revoked: bool,
147148
#[serde(with = "rfc3339")]
148149
pub created_at: NaiveDateTime,
149150
#[serde(with = "rfc3339::option")]

0 commit comments

Comments
 (0)