aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/access.md5
-rw-r--r--docs/openapi.yaml2
-rw-r--r--src/api/login.rs35
3 files changed, 32 insertions, 10 deletions
diff --git a/docs/access.md b/docs/access.md
index 7c576b0..a3b8864 100644
--- a/docs/access.md
+++ b/docs/access.md
@@ -100,8 +100,9 @@ curl -H "Authorization: Bearer ct1.…" http://other-node:3000/store
* **How long**: `--token-lifetime` (12 hours), refreshed (`POST /auth/refresh`, on any
node, which signs the new one) until `--session-lifetime` (7 days) after the login;
then the user logs in again. A refreshed token may say less (`limit`), never more,
- and live less (`lifetime`, in seconds): a dashboard opening another member's hands it
- one reading only, for five minutes ([`dashboard.md`](dashboard.md)).
+ and live less (`lifetime`, in seconds), and then ends its session with it: nobody
+ refreshes it past its end. A dashboard opening another member's hands it one reading
+ only, for five minutes ([`dashboard.md`](dashboard.md)).
* **Revoking**: a user disabled or removed is refused at once; all of a user's tokens,
by the user (`POST /auth/logout`) or by one who may manage users (`DELETE
/admin/users/{name}/tokens`), which sets `_continuum/logouts/<name>`: tokens issued
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index 960ab14..f0d54a9 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -1974,7 +1974,7 @@ components:
items: { $ref: '#/components/schemas/CapabilityRequest' }
lifetime:
type: integer
- description: Seconds the token lives, if less than `token-lifetime` (a token to hand over, say).
+ description: Seconds the token lives, if less than `token-lifetime` (a token to hand over, say). Its session ends with it, so it is not refreshed past its end.
TokenAnswer:
type: object
properties:
diff --git a/src/api/login.rs b/src/api/login.rs
index 3b4ee32..31a5d7f 100644
--- a/src/api/login.rs
+++ b/src/api/login.rs
@@ -248,6 +248,9 @@ fn new_login(
lifetime: Option<u64>,
) -> Claims {
let session_exp = now.saturating_add(state.session_lifetime.as_millis() as u64);
+ let exp = now
+ .saturating_add(lifetime_ms(state, lifetime))
+ .min(session_exp);
Claims {
v: 1,
swarm,
@@ -255,15 +258,23 @@ fn new_login(
user: user.to_owned(),
login: login.to_owned(),
iat: now,
- exp: now
- .saturating_add(lifetime_ms(state, lifetime))
- .min(session_exp),
+ exp,
session: token::new_session(),
- session_exp,
+ session_exp: ended_with(session_exp, exp, lifetime),
limit,
}
}
+/// When a session ends, `session_exp` at the latest, for a token ending at `exp`: with
+/// it, where it was asked to live less (`lifetime`). A token to hand over is refreshed
+/// past its end by nobody, its holder included.
+fn ended_with(session_exp: u64, exp: u64, lifetime: Option<u64>) -> u64 {
+ match lifetime {
+ Some(_) => session_exp.min(exp),
+ None => session_exp,
+ }
+}
+
/// `POST /auth/challenge`: one to sign for a login by key, valid for five minutes.
pub(crate) async fn challenge(State(state): State<AppState>) -> Result<Response, ApiError> {
let (_, now) = issuing(&state).await?;
@@ -426,14 +437,16 @@ async fn refreshed(
Some(limit)
}
};
+ let exp = now
+ .saturating_add(lifetime_ms(state, lifetime))
+ .min(earlier.session_exp);
let claims = Claims {
v: 1,
swarm,
issuer: state.local_id().to_owned(),
iat: now,
- exp: now
- .saturating_add(lifetime_ms(state, lifetime))
- .min(earlier.session_exp),
+ exp,
+ session_exp: ended_with(earlier.session_exp, exp, lifetime),
limit,
..(**earlier).clone()
};
@@ -534,4 +547,12 @@ mod tests {
assert!(!within(&[cap(&["logs"], &["read"])], &earlier));
assert!(!within(&[cap(&["notes"], &["read", "grant"])], &earlier));
}
+
+ /// A token asked to live less ends its session with it; another, the session goes on.
+ #[test]
+ fn a_short_token_ends_its_session() {
+ assert_eq!(ended_with(7_000, 300, Some(300)), 300);
+ assert_eq!(ended_with(7_000, 300, None), 7_000);
+ assert_eq!(ended_with(200, 300, Some(300)), 200);
+ }
}