add creation date to admin api tokens
All checks were successful
ci/woodpecker/push/debug Pipeline was successful
ci/woodpecker/pr/debug Pipeline was successful

This commit is contained in:
Alex 2025-03-12 09:52:39 +01:00
parent 325f79012c
commit 88b4623bf1
5 changed files with 37 additions and 8 deletions

View file

@ -2048,6 +2048,14 @@
"scope" "scope"
], ],
"properties": { "properties": {
"created": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "Creation date"
},
"expiration": { "expiration": {
"type": [ "type": [
"string", "string",

View file

@ -41,6 +41,7 @@ impl RequestHandler for ListAdminTokensRequest {
0, 0,
GetAdminTokenInfoResponse { GetAdminTokenInfoResponse {
id: None, id: None,
created: None,
name: "admin_token (from daemon configuration)".into(), name: "admin_token (from daemon configuration)".into(),
expiration: None, expiration: None,
expired: false, expired: false,
@ -54,6 +55,7 @@ impl RequestHandler for ListAdminTokensRequest {
1, 1,
GetAdminTokenInfoResponse { GetAdminTokenInfoResponse {
id: None, id: None,
created: None,
name: "metrics_token (from daemon configuration)".into(), name: "metrics_token (from daemon configuration)".into(),
expiration: None, expiration: None,
expired: false, expired: false,
@ -180,6 +182,10 @@ fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInf
GetAdminTokenInfoResponse { GetAdminTokenInfoResponse {
id: Some(token.prefix.clone()), id: Some(token.prefix.clone()),
created: Some(
DateTime::from_timestamp_millis(params.created as i64)
.expect("invalid timestamp stored in db"),
),
name: params.name.get().to_string(), name: params.name.get().to_string(),
expiration: params.expiration.get().map(|x| { expiration: params.expiration.get().map(|x| {
DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db") DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")

View file

@ -314,6 +314,8 @@ pub struct GetAdminTokenInfoRequest {
pub struct GetAdminTokenInfoResponse { pub struct GetAdminTokenInfoResponse {
/// Identifier of the admin token (which is also a prefix of the full bearer token) /// Identifier of the admin token (which is also a prefix of the full bearer token)
pub id: Option<String>, pub id: Option<String>,
/// Creation date
pub created: Option<chrono::DateTime<chrono::Utc>>,
/// Name of the admin API token /// Name of the admin API token
pub name: String, pub name: String,
/// Expiration time and date, formatted according to RFC 3339 /// Expiration time and date, formatted according to RFC 3339

View file

@ -1,6 +1,6 @@
use format_table::format_table; use format_table::format_table;
use chrono::Utc; use chrono::{Local, Utc};
use garage_util::error::*; use garage_util::error::*;
@ -30,11 +30,15 @@ impl Cli {
} }
pub async fn cmd_list_admin_tokens(&self) -> Result<(), Error> { pub async fn cmd_list_admin_tokens(&self) -> Result<(), Error> {
let list = self.api_request(ListAdminTokensRequest).await?; let mut list = self.api_request(ListAdminTokensRequest).await?;
let mut table = vec!["ID\tNAME\tEXPIRATION\tSCOPE".to_string()]; list.0.sort_by_key(|x| x.created);
let mut table = vec!["ID\tCREATED\tNAME\tEXPIRATION\tSCOPE".to_string()];
for tok in list.0.iter() { for tok in list.0.iter() {
let scope = if tok.scope.len() > 1 { let scope = if tok.expired {
String::new()
} else if tok.scope.len() > 1 {
format!("[{}]", tok.scope.len()) format!("[{}]", tok.scope.len())
} else { } else {
tok.scope.get(0).cloned().unwrap_or_default() tok.scope.get(0).cloned().unwrap_or_default()
@ -43,12 +47,15 @@ impl Cli {
"expired".to_string() "expired".to_string()
} else { } else {
tok.expiration tok.expiration
.map(|x| x.to_string()) .map(|x| x.with_timezone(&Local).to_string())
.unwrap_or("never".into()) .unwrap_or("never".into())
}; };
table.push(format!( table.push(format!(
"{}\t{}\t{}\t{}\t", "{}\t{}\t{}\t{}\t{}",
tok.id.as_deref().unwrap_or("-"), tok.id.as_deref().unwrap_or("-"),
tok.created
.map(|x| x.with_timezone(&Local).date_naive().to_string())
.unwrap_or("-".into()),
tok.name, tok.name,
exp, exp,
scope, scope,
@ -209,8 +216,9 @@ impl Cli {
fn print_token_info(token: &GetAdminTokenInfoResponse) { fn print_token_info(token: &GetAdminTokenInfoResponse) {
format_table(vec![ format_table(vec![
format!("ID:\t{}", token.id.as_deref().unwrap_or("-")), format!("ID:\t{}", token.id.as_ref().unwrap()),
format!("Name:\t{}", token.name), format!("Name:\t{}", token.name),
format!("Created:\t{}", token.created.unwrap().with_timezone(&Local)),
format!( format!(
"Validity:\t{}", "Validity:\t{}",
token.expired.then_some("EXPIRED").unwrap_or("valid") token.expired.then_some("EXPIRED").unwrap_or("valid")
@ -219,7 +227,7 @@ fn print_token_info(token: &GetAdminTokenInfoResponse) {
"Expiration:\t{}", "Expiration:\t{}",
token token
.expiration .expiration
.map(|x| x.to_string()) .map(|x| x.with_timezone(&Local).to_string())
.unwrap_or("never".into()) .unwrap_or("never".into())
), ),
format!("Scope:\t{}", token.scope.to_vec().join(", ")), format!("Scope:\t{}", token.scope.to_vec().join(", ")),

View file

@ -1,6 +1,7 @@
use base64::prelude::*; use base64::prelude::*;
use garage_util::crdt::{self, Crdt}; use garage_util::crdt::{self, Crdt};
use garage_util::time::now_msec;
use garage_table::{EmptyKey, Entry, TableSchema}; use garage_table::{EmptyKey, Entry, TableSchema};
@ -24,6 +25,9 @@ mod v2 {
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct AdminApiTokenParams { pub struct AdminApiTokenParams {
/// Creation date
pub created: u64,
/// The entire API token hashed as a password /// The entire API token hashed as a password
pub token_hash: String, pub token_hash: String,
@ -91,6 +95,7 @@ impl AdminApiToken {
let ret = AdminApiToken { let ret = AdminApiToken {
prefix, prefix,
state: crdt::Deletable::present(AdminApiTokenParams { state: crdt::Deletable::present(AdminApiTokenParams {
created: now_msec(),
token_hash: hashed_token, token_hash: hashed_token,
name: crdt::Lww::new(name.to_string()), name: crdt::Lww::new(name.to_string()),
expiration: crdt::Lww::new(None), expiration: crdt::Lww::new(None),