API tokens a person makes for scripts and agents to act as them (#123)
The API took a session cookie, a proxy's word or the shared admin token, so a script or an agent working for one person had to sign in with their password and carry the cookie, or be given the admin token. Settings now makes named tokens, ipx_ and 256 random bits, sent as Authorization: Bearer. A token is its owner and no more. Only its SHA-256 is kept, in the new api_tokens table, with when it was made and last used; it is shown once and revoked from the same list. An unknown or revoked one gets a 401 rather than falling through to a cookie. Cloudflare Access still stands in front of the tunnel, so from outside a token needs an Access service token beside it; docs/sso.md says how. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
28
src/auth.rs
28
src/auth.rs
@@ -40,6 +40,19 @@ pub fn new_session_token() -> String {
|
||||
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
|
||||
/// A new API token: `ipx_` and a session's 256 random bits, the prefix so one found in a log
|
||||
/// or a file says what it opens (#123).
|
||||
pub fn new_api_token() -> String {
|
||||
format!("ipx_{}", new_session_token())
|
||||
}
|
||||
|
||||
/// What is kept of an API token. A fast hash is enough: the token is 256 random bits, not a
|
||||
/// password, so there is nothing to guess from a stolen hash.
|
||||
pub fn api_token_hash(token: &str) -> String {
|
||||
use sha2::Digest;
|
||||
sha2::Sha256::digest(token.as_bytes()).iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
|
||||
fn getrandom(buf: &mut [u8]) -> std::io::Result<()> {
|
||||
use std::io::Read;
|
||||
std::fs::File::open("/dev/urandom")?.read_exact(buf)
|
||||
@@ -74,6 +87,21 @@ mod tests {
|
||||
assert!(hash_password("short").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_tokens_are_prefixed_and_hash_to_hex() {
|
||||
let t = new_api_token();
|
||||
assert!(t.starts_with("ipx_") && t.len() == 68);
|
||||
let h = api_token_hash(&t);
|
||||
assert_eq!(h.len(), 64);
|
||||
assert_eq!(h, api_token_hash(&t), "the same token, the same hash");
|
||||
assert_ne!(h, api_token_hash(&new_api_token()));
|
||||
assert_eq!(
|
||||
api_token_hash("abc"),
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
"SHA-256"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_tokens_are_long_and_distinct() {
|
||||
let a = new_session_token();
|
||||
|
||||
Reference in New Issue
Block a user