Skip to content

Commit d7dad02

Browse files
committed
Auto merge of #2676 - mibac138:master, r=jtgeibel
Add `DELETE /tokens/current` endpoint Fixes #2653
2 parents 3c448ad + 7c42ae1 commit d7dad02

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

src/controllers/token.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::schema::api_tokens;
55
use crate::util::read_fill;
66
use crate::views::EncodableApiTokenWithToken;
77

8+
use conduit::{Body, Response};
89
use serde_json as json;
910

1011
/// Handles the `GET /me/tokens` route.
@@ -108,3 +109,18 @@ pub fn revoke(req: &mut dyn RequestExt) -> EndpointResult {
108109
struct R {}
109110
Ok(req.json(&R {}))
110111
}
112+
113+
/// Handles the `DELETE /tokens/current` route.
114+
pub fn revoke_current(req: &mut dyn RequestExt) -> EndpointResult {
115+
let authenticated_user = req.authenticate()?;
116+
let api_token_id = authenticated_user
117+
.api_token_id()
118+
.ok_or_else(|| bad_request("token not provided"))?;
119+
120+
let conn = req.db_conn()?;
121+
diesel::update(api_tokens::table.filter(api_tokens::id.eq(api_token_id)))
122+
.set(api_tokens::revoked.eq(true))
123+
.execute(&*conn)?;
124+
125+
Ok(Response::builder().status(204).body(Body::empty()).unwrap())
126+
}

src/router.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub fn build_router(app: &App) -> RouteBuilder {
8080
api_router.get("/me/tokens", C(token::list));
8181
api_router.put("/me/tokens", C(token::new));
8282
api_router.delete("/me/tokens/:id", C(token::revoke));
83+
api_router.delete("/tokens/current", C(token::revoke_current));
8384
api_router.get(
8485
"/me/crate_owner_invitations",
8586
C(crate_owner_invitation::list),

src/tests/token.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,74 @@ fn revoke_token_success() {
271271
});
272272
}
273273

274+
#[test]
275+
fn revoke_current_token_success() {
276+
let (app, _, user, token) = TestApp::init().with_token();
277+
278+
// Ensure that the token currently exists in the database
279+
app.db(|conn| {
280+
let tokens: Vec<ApiToken> = assert_ok!(ApiToken::belonging_to(user.as_model())
281+
.filter(api_tokens::revoked.eq(false))
282+
.load(conn));
283+
assert_eq!(tokens.len(), 1);
284+
assert_eq!(tokens[0].name, token.as_model().name);
285+
});
286+
287+
// Revoke the token
288+
let response = token.delete::<()>("/api/v1/tokens/current");
289+
assert_eq!(response.status(), StatusCode::NO_CONTENT);
290+
291+
// Ensure that the token was removed from the database
292+
app.db(|conn| {
293+
let tokens: Vec<ApiToken> = assert_ok!(ApiToken::belonging_to(user.as_model())
294+
.filter(api_tokens::revoked.eq(false))
295+
.load(conn));
296+
assert_eq!(tokens.len(), 0);
297+
});
298+
}
299+
300+
#[test]
301+
fn revoke_current_token_without_auth() {
302+
let (_, anon) = TestApp::init().empty();
303+
304+
let response = anon.delete::<()>("/api/v1/tokens/current");
305+
assert_eq!(response.status(), StatusCode::FORBIDDEN);
306+
assert_eq!(
307+
response.json(),
308+
json!({ "errors": [{ "detail": "must be logged in to perform that action" }] })
309+
);
310+
}
311+
312+
#[test]
313+
fn revoke_current_token_with_cookie_user() {
314+
let (app, _, user, token) = TestApp::init().with_token();
315+
316+
// Ensure that the token currently exists in the database
317+
app.db(|conn| {
318+
let tokens: Vec<ApiToken> = assert_ok!(ApiToken::belonging_to(user.as_model())
319+
.filter(api_tokens::revoked.eq(false))
320+
.load(conn));
321+
assert_eq!(tokens.len(), 1);
322+
assert_eq!(tokens[0].name, token.as_model().name);
323+
});
324+
325+
// Revoke the token
326+
let response = user.delete::<()>("/api/v1/tokens/current");
327+
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
328+
assert_eq!(
329+
response.json(),
330+
json!({ "errors": [{ "detail": "token not provided" }] })
331+
);
332+
333+
// Ensure that the token still exists in the database after the failed request
334+
app.db(|conn| {
335+
let tokens: Vec<ApiToken> = assert_ok!(ApiToken::belonging_to(user.as_model())
336+
.filter(api_tokens::revoked.eq(false))
337+
.load(conn));
338+
assert_eq!(tokens.len(), 1);
339+
});
340+
}
341+
274342
#[test]
275343
fn using_token_updates_last_used_at() {
276344
let url = "/api/v1/me";

0 commit comments

Comments
 (0)