implement JWT token
This commit is contained in:
parent
291cc55c4d
commit
5c86d4e899
4 changed files with 136 additions and 0 deletions
|
@ -12,3 +12,6 @@ signal-hook = "0.3.17"
|
|||
log = "0.4.22"
|
||||
env_logger = "0.11.6"
|
||||
anyhow = "1.0.95"
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
base64 = "0.22.1"
|
||||
serde_json = "1.0.134"
|
1
bot/src/bluesky.rs
Normal file
1
bot/src/bluesky.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod token;
|
131
bot/src/bluesky/token.rs
Normal file
131
bot/src/bluesky/token.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use anyhow::anyhow;
|
||||
use base64::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Represents the token's internal payload.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct TokenPayloadInternal {
|
||||
sub: String,
|
||||
iat: u64,
|
||||
exp: u64,
|
||||
aud: String,
|
||||
}
|
||||
|
||||
/// Token represents a bluesky authentication token.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialOrd, PartialEq, Default)]
|
||||
struct Token {
|
||||
handle: String,
|
||||
#[serde(rename(serialize = "accessJwt", deserialize = "accessJwt"))]
|
||||
access_jwt: String,
|
||||
#[serde(rename(serialize = "refreshJwt", deserialize = "refreshJwt"))]
|
||||
refresh_jwt: String,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
/// Returns true if the token is expired, false otherwise.
|
||||
fn is_expired(&self) -> Result<bool, anyhow::Error> {
|
||||
let parts: Vec<&str> = self.access_jwt.split('.').collect();
|
||||
let payload_part = parts.get(1).ok_or(anyhow!("Missing payload from token"))?;
|
||||
|
||||
let result = BASE64_STANDARD_NO_PAD.decode(payload_part)?;
|
||||
let payload: TokenPayloadInternal = serde_json::from_slice(&result)?;
|
||||
let now = SystemTime::now();
|
||||
let unix_epoch_seconds = now.duration_since(UNIX_EPOCH)?.as_secs();
|
||||
Ok(unix_epoch_seconds - 60 >= payload.exp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Token {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Token [Handle: {}, AccessJWT: {}, RefreshJWT: {}]",
|
||||
self.handle,
|
||||
self.access_jwt.get(0..5).unwrap_or(&self.access_jwt),
|
||||
self.refresh_jwt.get(0..5).unwrap_or(&self.refresh_jwt),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow;
|
||||
|
||||
#[test]
|
||||
fn test_is_expired_true() -> Result<(), anyhow::Error> {
|
||||
// Setup
|
||||
let payload = TokenPayloadInternal {
|
||||
sub: "".to_string(),
|
||||
iat: 0,
|
||||
exp: 0,
|
||||
aud: "".to_string(),
|
||||
};
|
||||
let json_data = serde_json::to_string(&payload)?;
|
||||
let base64_data = BASE64_STANDARD_NO_PAD.encode(json_data);
|
||||
|
||||
let mut token = Token::default();
|
||||
token.access_jwt = format!("eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.{}.oWhKfhGWv6omS3oFQ21GX29uzsd5WrfPJyotJMCQ8V44GF1UN2et7sf_JKVB5jkSuJa6kVWERGuKVGgj8AWScA", base64_data);
|
||||
|
||||
// Test
|
||||
let result = token.is_expired()?;
|
||||
|
||||
// Assert
|
||||
assert_eq!(result, true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_expired_false() -> Result<(), anyhow::Error> {
|
||||
// Setup
|
||||
let now = SystemTime::now();
|
||||
let unix_epoch_seconds = now.duration_since(UNIX_EPOCH)?.as_secs() + 100_000;
|
||||
let payload = TokenPayloadInternal {
|
||||
sub: "".to_string(),
|
||||
iat: 0,
|
||||
exp: unix_epoch_seconds,
|
||||
aud: "".to_string(),
|
||||
};
|
||||
let json_data = serde_json::to_string(&payload)?;
|
||||
let base64_data = BASE64_STANDARD_NO_PAD.encode(json_data);
|
||||
|
||||
let mut token = Token::default();
|
||||
token.access_jwt = format!("eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.{}.oWhKfhGWv6omS3oFQ21GX29uzsd5WrfPJyotJMCQ8V44GF1UN2et7sf_JKVB5jkSuJa6kVWERGuKVGgj8AWScA", base64_data);
|
||||
|
||||
// Test
|
||||
let result = token.is_expired()?;
|
||||
|
||||
// Assert
|
||||
assert_eq!(result, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_deserialize() -> Result<(), anyhow::Error> {
|
||||
let data = r#"
|
||||
{
|
||||
"handle": "cool-bot.bsky.social",
|
||||
"email": "cool@gmail.com",
|
||||
"emailConfirmed": true,
|
||||
"emailAuthFactor": false,
|
||||
"accessJwt": "ein.zwei.drei",
|
||||
"refreshJwt": "fier.funf.sechs",
|
||||
"active": true
|
||||
}
|
||||
"#;
|
||||
|
||||
let token: Token = serde_json::from_str(data)?;
|
||||
|
||||
assert_eq!(
|
||||
token,
|
||||
Token {
|
||||
handle: "cool-bot.bsky.social".to_string(),
|
||||
access_jwt: "ein.zwei.drei".to_string(),
|
||||
refresh_jwt: "fier.funf.sechs".to_string(),
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
mod bluesky;
|
||||
mod cli;
|
||||
|
||||
//noinspection DuplicatedCode
|
||||
|
|
Loading…
Reference in a new issue